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:
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:
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:
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.
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.
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:
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.
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.
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("");
} // if
} // if
} // RenderContents
} // class OptGroupDropDownList
} // namespace
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.
I am, in case you didn't know, left-handed. I love being left-handed, the greatest men in history were predominantly left-handed (so were some of the worst). Even God is left-handed. Don't believe me? He must be, since the Bible says that Jesus is sitting on His right hand.
Anyways, there are many, many places in which being left-handed puts me at a disadvantage. Some are trivial - did you know that the printing on pens and pencils is upside-down if you hold them in your left hand? Some are minor - scissors are built so that when held in your right hand, the top blade does not obscure your view of the cut line - when held in the left hand, you must look over the blade to see your cut line. Some can be dangerous - in fact left-handers may actually live shorter lives.
Here's one I'll bet no one has noticed before. The shortcut keys for the common editing commands undo, cut, copy, and paste use the shortcuts Z, X, C, and V respectively. All of those keys are on the left side of the keyboard. So, when I am using my mouse and I want to use one of those common shortcuts, I must take my hand off of the mouse, press the desired key chord, then return to the mouse. If I have a difficult series of edits to make (this is common when I am programming) this can be very inefficient. Sometimes I even move the mouse to the right side of the keyboard for a few minutes (another advantage to being left-handed is that most of us can use out right hands fairly well for common tasks).
Probably not the biggest issue in my life, but it sure can be irritating. I can only hope that all that movement reduces the risk of repetitive motion injuries.
I have read all about this pattern for doing UI work, but I have never seen a great need to adopt the pattern. Both Builder and Visual Studio invite you to place all of your response code directly into the UI element class, and I have gladly taken the invitation. To be honest, I had never seen a good reason to do any differently. Oh, I would wrap some concepts into external (or even internal) classes, but the basic approach has worked fine for me.
The last couple of days I have been working on the configuration assistant for the accounting system at mypeoplematter. This is an ASP Wizard control with (as of now) 11 pages, some of which feature expanding windows that allow the user to configure things like bank accounts. Very quickly the number of buttons and their callbacks became unmanageable. So I built a controller class that provided a framework for each page. Then each "page" in the wizard got its own Controller-derived class to actually handle the work. My form class was left with just a little code to abstract out navigation events, and 4 general-purpose button events that it forwards to the controllers.
The Controller class has an interface with 7 methods:
virtual public void OnEnter(secure_accounting_ConfigAssistant page);
virtual public void OnExit(secure_accounting_ConfigAssistant page);
virtual public bool SkipMe(secure_accounting_ConfigAssistant page);
virtual public void OnSelect(secure_accounting_ConfigAssistant page, object sender);
virtual public void OnShowEditor(secure_accounting_ConfigAssistant page);
virtual public void OnSaveEditor(secure_accounting_ConfigAssistant page);
virtual public void OnCancelEditor(secure_accounting_ConfigAssistant page);
It provides (empty) default implementations so that derived classes need only override the methods they need. I built a small caching mechanism to keep track of controllers throughout postbacks.
The resulting code is so clean that I will use it in every Wizard I build from here one, and probably for multi-tab pages as well. I can find the implementation of a given event handler by going to the appropriate controller and looking through at most 7 methods, rather than the over 60 that I would have needed without the controller abstraction.
Having said all that, I still do not see how MVC (or MVVC) would simplify my pages that perform a single function. Now, I have done some abstracting - most of my data entry pages have FillPage() and Gather() methods that handle the populating and retrieval from these pages. But since the pages themselves perform only a single function, I do not see the benefit to be gained from adding a controller class.
Last week was Vacation Bible School. One of the things I love about VBS is all the songs with lots of hand motions and dancing. The reason we do it this way is because adding the actions helps the kids to stay interested and to learn. When we make big hand motions and sing about how big or powerful God is, the motions reinforce the message.
Our word today is “demonstration.” One aspect of our role as worship leaders is that of demonstrating what we are saying to the congregation. When we raise our hands in surrender, when we clap with joy, when we dance (or bounce) to a song of celebration, our actions reinforce the message we are singing. The principle we use when teaching children applies to adults as well – many people in our congregation are learning about God and how to worship Him by watching us. Let us demonstrate our words with our actions, just like we did last week at VBS.
At the heart of my new website is a little control that does a dynamic lookup of a name as you type in a text box. Internally, I call it a PartyLookup (as the core table it searches is the Party table). Today, I am trying to add one of these controls to a rather nasty page: it has a wizard inside an update panel all in a modal popup extender. This all worked when the popup window did not have a wizard, but the page is complex enough that I decided I needed to break it up into 2 steps, hence the wizard. But my lookup control stopped working, as the dynamic javascript I generate to handle the AJAX calls was not getting emitted via the partial postback when I changed wizard steps (the lookup control is on the second page).
I searched Google for an hour, trying everything I could find. Finally, the following 2 changes worked:
For javascript I generate in the server-side code, I needed to use
ClientScript.RegisterClientScriptBlock, rather than Page.ClientScript.RegisterClientScriptBlock. This is a mysterious fix, but it works.
Second, for script that I want to include from an external source, I needed to add:
if (typeof (Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();
Together, these two calls seem to have gotten my control working in this new environment. We will see...
As anyone who knows me can attest. I am a fiscal conservative politically. My first presidential vote was for Ronald Reagan, and I considered it the best gift a young voter could have been given. I am against tax increases, period.
But I don't understand Grover Nordquist's position on the ethanol subsidies and other similar issues. I want the government to simplify the tax code, and I want the government to stop picking winners and losers. Subsidies go against both. Technically, ending a subsidy has the same effect as raising taxes, but when you have such a narrowly defined subsidy, the elimination of it feels more like a reform to me.
And these are the sorts of reforms we need. Eliminate preferences for one style of business over another. Eliminate preferences for one behavior over another. Let Americans decide what they want to buy without a parental government telling them what is best for them.
My wife and I head the worship team at our church. We get to be on stage occasionally, but mostly this involves us training our young team. It's a lot of fun, and I always tell my pastor that I hadn't meant to sign up for youth ministry.
One of the things I do is what I call a "worship word." Every week at practice, I take a couple of minutes and do a short teaching around a single word and its application to worship. I'm going to start posting them here every Thursday or Friday. This week's word was "approach."
-----
The Bible tells us that we are to "boldly approach the throne of grace." It is here that we meet the God who gives grace and mercy and help. As worshipers, we do this in part when we worship. Through our singing and playing, we approach the throne of the God who meets with us. I am coming to a new understanding of the remarkable nature of our relationship with this God. He is the one who dwells in inapproachable light, whom if we were to look upon him we would die; and yet we can approach him because he invites us. Just as the shed blood of Christ opens the way for us to share in his table, so it has purchased for us an invitation to approach the inapproachable God.
As a worship team, we have an additional gift. Not only do we approach God, but we are given the task of leading others as they approach him to. Our leadership in worship is intended to open the door for the congregation to follow us into the presence of the God of the Universe. What a wonderful, inexpressible opportunity!
I always find it interesting when I suddenly find a use for a feature of a toolkit that I never needed before. This is the story of one such event, which just happened today.
Some background: I'm working on the accounting portion of my new company's product. It is almost ready to unveil; I just have to finish a couple of data entry screens and generate the core reports. Currently, I'm working on the bank account reconciliation page.
I want it to be nicely interactive - when you check an item off, I want to immediately update the item counts and totals so that the user can see their progress toward balanced. The most effective way to do this is to use javascript, and so that is what I did.
It worked great. But I have a handful of things that require a server round-trip, or a post back. When these happened, my totals were reset. This makes sense, the server-side couldn't possibly know what all had happened since it last sent the pages. This means that I have to figure out what the user had been doing, and have my server-side code reproduce the calculations the client-side was doing (I know I could have stored the values in hidden fields, I chose not to and I'm glad, because then this story wouldn't have happened).
Where to do these calculations? The answer is to handle them in Page_Load. I've done lots of coding in Page_Load, but never in response to a post-back. My pages mostly look a lot like this:
protected void Page_Load(object sender, EventArgs e) {
if (IsPostBack) {
} else {
// Lots of code here to load the page from
// query strings and other stuff.
}
}
I almost never had any code in the side of the if statement when IsPostBack was true. I had about decided I never would. But here was a case where I needed to fill my page only on a post-back.
Nothing really profound, but I just think it is interesting when something new comes up for me.
I have been trying to figure out how the blog fits in my life. Basically, it hasn't for a couple of years now.
But I love to write, and writing helps me to organize my thoughts. So I have a new plan. I'm going to try and post at least 3 times a week. The posts will be as varied as my interests - one may be something new that I discovered about programming while working; another may come from politics or religion; others may just be from everyday life. I will try to make it all interesting, and hopefully the blog will be discovered again. But even if not, this will be the place where I can look back an remember what has happened.
But this post has another, more sinister purpose (cue music) - I want to get a link back to my new company's web site in place. So, here is a shameless plug/postback to the site in an effort to get Google to notice it. You can find my company at: www.mypeoplematter.com. The company makes church management software that runs in a browser. It is designed primarily for smaller churches - it doesn't have all of the bells and whistles of the well-established packages. But it is my experience that most churches don't want all of those things. They just need a few features: contact management, donation tracking, directories and communications, and accounting. When I finish the accounting piece in the next few weeks, I will have all of these features. And my pricing is designed for smaller churches, just a minimal monthly subscription fee, tiered based on church size. A church of under 120 will pay just $25/month for the entire package, only $15/month if they don't need accounting.
Anyways, that's what's up. If you're still out there, it's nice to be speaking again.