Tuesday, July 19, 2011

Styling optgroups

An earlier post noted that I had managed to go a long time without learning about the optgroup feature that allows items in a drop down list to be grouped under sub-headings. Yesterday, I wanted to change the default look, so I did a quick google search to see if anyone had any nice styling examples that I could adapt - this is my normal design policy as I am a poor original designer.

Basically, there were none (this makes me feel better - apparently I am not the only web developer who has overlooked this feature). So I set out to develop my own. My first style rule was a simple selector on optgroup:

optgroup {
    background-color: #023373;
    color: #ffffff;
}

I was hoping just the group title would be in dark blue with white text, but instead the entire group was—which meant that then entire list was so colored. So I added a second rule:
optgroup option {
    background-color: white;
    color: black;
}
This got it. Now my headers were blue bars with white text, but the rest of the list was back to normal. All that remained was to give the group titles a bit better look: I wanted them centered with a little spacing. My final rules:

optgroup {
    background-color: #023373;
    color: #ffffff;
    font-weight: bold;
    font-style: normal;
    padding: 3px 0 0 0;
    text-align: center;
}
        
optgroup option {
    background-color: #ffffff;
    color: #000000;
    padding: 0 0 0 1em;
    text-align: left;
}

Note that I had to reset the styling for the option. I initially had left off the padding rule for the option elements, and the actual list items were flush left.

Here's an example of the final look:



Friday, July 15, 2011

Design by accident

Wow! 3 in one day...

I am a poor original designer. That is, if you give me a blank page, my designs will be at best passable. I'm not too bad at improving a design, so I can be a useful member of a design team, as long as someone else has the original ideas.

One consequence of this is that my best designs are accidental. Case in point: today, I decided that I did not like the graphical buttons that I was using throughout mypeoplematter. I went to the css button generator site to try out a button using the overall color scheme for the site. I ended up with something that I liked, so I updated my style sheets and voila! New buttons. I liked them a lot better, but because I had used a very small padding value in the style sheets, some were exceptionally small. So, I added a min-width attribute to the style. This was the design accident.

When I went to look at the buttons, they were left-justified. And I loved it! I have not seen left-justified buttons on a web site before, so I had no idea how cool they could look.

Example

An example above. I have to admit, I like this significantly better than centered buttons. I wonder, has anyone else an opinion?

UpdatePanel animations and focus

My site at mypeoplematter makes extensive use of UpdatePanels to provide a more pleasant user experience. For pages that submit data to the database (and might therefore take some time to return), I use UpdatePanelAnimationExtenders to give a visual indication that something is happening. So far, so good. My pages don't flash, and the user knows when they have to wait.

I have a page for entering weekly donations. Most churches do this in a large batch, so the page is designed to be very easy to use: the tab order is very predictable, you can press the Enter key when you have entered all the necessary information, etc. Unfortunately, when I add an UpdatePanel and the AnimationExtender to this page, I lost a critical usability feature: the page did not set the focus to the proper control.

This is one of those problems that is well documented on the web, but not well solved. I found my answer here, in the midst of a lot of incorrect suggestions. Whet I have done is added an AnimationExtender to the TextBox that I want to have control. That AnimationExtender uses the OnLoad animation to set the focus. It's a little kludgy, it would be nice if the UpdatePanelAnimationExtender did it correctly in the first place.

But what's really nice is that I was able to hide the code in a custom control I had already written. The UpdatePanelAnimationExtender is complex to specify, and requires some javascript to make it all work. I had already encapsulated all of that into a composite control that I could just drop onto my page thus:

<mypeople:UpdateAnimation ID="updateAnimation" runat="server" AnimationTargetID="wholePage" UpdatePanelID="upnlContent" FocusControlID="donorLookup" />

I added the FocusControlID attribute, and added some code to generate the necessary controls and javascript:

protected override void CreateChildControls() {
 ... other code for the update panel

 // If there is a FocusControlID, add an extender to set the focus
 focusAE = null;
 if (!String.IsNullOrEmpty(FocusControlID)) {
  UpdatePanel upnl = Page.MuchBetterFindControl(UpdatePanelID);
  focusAE = new AjaxControlToolkit.AnimationExtender {
   TargetControlID = FocusControlID, 
   Animations = "",
  };
  upnl.Controls[0].Controls.Add(focusAE);
 } // if

 ... code to build the page
} // CreateChildControls

protected override void OnPreRender(EventArgs e) {
 if (!String.IsNullOrEmpty(FocusControlID)) {
  Control theControl = NamingContainer.FindControl(FocusControlID);
  String controlID = FocusControlID;
  if (theControl != null) {
   if (theControl is PartyLookup) {
    controlID = ((PartyLookup)theControl).TextBoxClientID;
   } else {
    controlID = theControl.ClientID;
   } // if
  } // if
  js += "\nfunction OnLoad_FocusField() { UpdateExtenderFocusField('" + 
        controlID + "'); }";
 } // if
} // OnPreRender

The method MuchBetterFindControl is from here. It searches the hierarchy of control for the control with the indicated ID.

An interesting note: I cannot create the UpdatePanelAnimationExtender inside the UpdatePanel, but I have to create the focus control's AnimationExtender inside the UpdatePanel. So, in the code for CreateChildControls, I make sure to add the extender to the UpdatePanel's controls, not to those of the composite control.

All in all, this turned out to be a nifty, relatively clean solution to a problem that remains unsolved in most of the places I saw it on the Internet. If you would like the code, email me and I will bundle up the pieces for you.

Worship word: presents

Theologians (and I'm sure some who might read this post will be irritated) speak of the attributes of God. This is a useful way to try to understand what He has told us about Himself in the Bible, but it can sometimes get so theoretical and dry that it threatens to suck the life out of the Living God Himself.

Consider God's attribute of immutability. That this attribute is His is undeniable. 1 Samuel 15:29 and Malachi 3:6 say so explicitly. But note that these passages say that God's mind does not change, i.e., His plans and purposes for today are the same as they were yesterday and as they will be for all eternity.

What does this have to do with worship? You see, theologians get snagged on this concept. They extend it beyond this idea of immutable purpose to mean that things that happen have no impact on Him. But the Bible tells us that God takes pleasure in our worship, it tells us that He seeks worshipers. When we worship, we bring presents to the God of the universe, and He is affected by them. He created us for relationship, and our worship is an expression of that relationship. Think of it - the creator God is moved by my efforts to have relationship with Him.

I am always amazed that so wonderful a woman as Bethany chooses to love me - how much more amazing is the love of the God of Gods.

Monday, July 11, 2011

Learning

I have been writing web pages for a long time now. I started teaching HTML in 1999, and have worked for most of the last decade on projects that revolved around web pages. One would think that I probably knew most of the details of HTML - it is not a particularly complex language. Even if I didn't know all of the esoterica, surely I had all of the broad strokes, right? Well...

I was working on the accounting application within mypeoplematter. When the user needs to enter a transaction, they select an account from their chart of accounts. I am using a standard select list, which translates into a drop-down list in the web browser:



The list was too long (the basic chart of accounts has over 50 accounts in it), and there was no indication of the type of account (income / expense / bank / liability) in the list. I wanted to layout the list so that the account type was on the far-right, with the account name left-justified, but this is not supported by the HTML spec, so I was looking around the web for other ideas.

I found someone's code that had provided something like this, and was looking through it to try and understand what they did. In the midst of the code, there was something about optgroups. I didn't know what that was, so I googled it. Turns out, you can group items in select lists using optgroup. Over a decade of experience, and here was something new. Not only that, but it solves my problem perfectly. By the way, for programmers who find this, I will post at the end a small class that extends the standard .NET DropDownList to support retrieving the optgroup from the data source.




But my real point here is not about optgroups. My point is about knowledge. It seemed entirely reasonable to me to assume that I knew most of the features of HTML 4 - it is, after all, pretty simple. But here was something I had simply never encountered. It was a humbling moment. And of course, it made me realize that this experience is probably much more common than I realize. It is probably so for all of us. I know this is true: it just pays to be reminded:
It is not the things we know that give us the most trouble; it's not even the things we know we don't know; it's the things we don't know we don't know that are the biggest issue.
Humility doesn't always come easy for me. It's helpful to be reminded.

Programmers - here, in its entirety, is my DropDownList extension - it doesn't format well on my blog site, but if you select it and paste it into a text editor, it's all there:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using MyPeople.Extensions;

/// 

/// This class extends the built-in DropDownList to provide support for retriving the name of an optgroup from the database.
/// 
namespace MyPeople.WebControls {
    public class OptGroupDropDownList : DropDownList {
        public OptGroupDropDownList() { }

        /// 
        /// The field in the datasource that provides the value for the optgroup. IF not set, then the list behaves as the built-in dropdownlist.
        /// 
        public string OptGroupField {
            get {
                object o = ViewState["OptGroupField"];
                return (o == null) ? "" : (string)o;
            } // get
            set {
                ViewState["OptGroupField"] = value;
            } // set
        } // OptGroupField

        protected override void RenderContents(HtmlTextWriter writer) {
            if (String.IsNullOrEmpty(OptGroupField)) {
                base.RenderContents(writer);
            } else {
                bool useOptGroup = !String.IsNullOrEmpty(OptGroupField);
                String lastOptGroup = null;
                ObjectDataSource ds = Page.MuchBetterFindControl(DataSourceID);
                IEnumerable objs = ds.Select();
                foreach (object item in objs) {
                    String text = DataBinder.Eval(item, DataTextField, DataTextFormatString);
                    String val = "";
                    if (!String.IsNullOrEmpty(DataValueField)) {
                        val = DataBinder.Eval(item, DataValueField, "{0}");
                    } // if
                    bool sel = false;
                    if (!String.IsNullOrEmpty(SelectedValue)) {
                        sel = val == SelectedValue;
                    } // if

                    if (useOptGroup) {
                        string thisGroup = DataBinder.Eval(item, OptGroupField, "{0}");
                        if (thisGroup != lastOptGroup) {
                            if (lastOptGroup != null) writer.WriteLine("");
                            writer.WriteLine("", thisGroup);
                            lastOptGroup = thisGroup;
                        } // if
                    } // if
                    writer.WriteLine("", System.Web.HttpUtility.HtmlEncode(text), System.Web.HttpUtility.HtmlEncode(val), sel ? " selected='selected'" : "");
                } // foreach

                if (useOptGroup) {
                    writer.WriteLine("");
                } // if
            } // if
        } // RenderContents
    } // class OptGroupDropDownList
} // namespace


Monday, July 04, 2011

Naming

Happy 4th of July. It is a good thing to celebrate the birthday of the greatest nation in all of history. Not perfect, but the best we humans have done thus far.

I am working a bit today, mostly because we are going to be going away for a few days later this week and wanted to finish a particularly useful piece of code for the accounting report system. Along the way, I discovered a flaw in my design. These are always frustrating; especially when, as in this case, the design flaw touches on several aspects of the software and thus testing the fix is more time-consuming and tedious. But this design flaw was particularly frustrating, because there were signs of the problem that I overlooked. The most significant sign that I had a problem in my design was to be found in the names that I was using.

Briefly, the flaw was that I was only requiring that a single item be configured where I in fact needed two related items. As I went through my code making the change, I found that I had been confused all along, as I sometimes used one name when describing the configured item, while at other times I use another name. The two names accurately described the two items I needed, but I missed the hint until just today.

I mention this because this is one of those principles of programming that I have developed over my career: if I don't know what to name something, then I don't understand it. And if I don't understand it, I haven't fully investigated it. Normally, if I have trouble with a name, I back away from the code and attempt to clarify my understanding before I move forward. In this particular case, I let my (mistaken) belief that I knew what I needed obscure the hint that the naming issue was giving me.

I love this for another reason. The issue of naming is one of those things that touches on multiple disciplines. The centrality of naming is one of the reasons that I have confidence in the Genesis story - the God who made us understands that if we cannot name something, we don't understand it. So, one of His first tasks for man was to name the animals. It matters not to me whether you believe in the historicity of the Genesis account; it is difficult to deny the insight of the second chapter of Genesis in this matter.

This issue of naming touches in the political realm as well. One of the great powers of the media is that it often gets to name the forces within our society. The power of naming allows the namer to define the item named. It is why politicians look to name bills. The name of the bill becomes its meaning, even if they are not in fact the same thing. To name is to define and to control understanding.

Finally, I have found this principle true in my own life. Early in my adult life, I was struggling with some personal issues. I flailed away, trying to get a handle on what I was doing, until a friend gave me a name. In retrospect, the name was only partly accurate. But at the time, the name gave me something to address. In addressing the name, I found the ability to overcome the underlying issue and to move past a continuing area of struggle.

I recommend it as a life principle. If you do not understand what is happening in life, try to name it. The process of naming can often bring the understanding that has heretofore eluded you.

It also works if you write software...