ASP.NET – Client Side List Box

February 24, 2008

We needed to create a dropdown list that would allows us to reorder items on the list. Piece o’ cake, right? We created a composite web control with a drop down list and 3 client side buttons. A bunch of javascript managed adding the items and sorting the list. No big deal.

The interesting part didn’t happen until post back. Asp:ListBox doesn’t recognize things that are added from the client, only from the server. I thought it would work because you can do that type of things with, for example, a textbox. The different is that a textbox is an input control and a listbox is just a select control.

I created a new composite control called ClientListBox. I have know idea if I did it correctly, but it works. (I have minimal web control experience. I’m more about all of the other tiers. The composite control contains a listbox and a hidden field. As you add items to the list, the text and values are saved to the hidden field. On post back, it wipes out the list times, then creates new ones based on the contents of the hidden field.

The hidden fields stores the values as:
value\ttext\n

The IDs of the listbox and hidden fields are set dynamically based on the id of the composite control. So, if the control id is TEST, then the listbox is TEST_LB, and the hidden is TEST_HIDDEN.

Its intended use is client side script, so it gives you a known method name to retrieve the object.

If the control name is TEST, then in javascript, you can call getTEST() to obtain a reference to the object. You can then all methods like Add(text, value), etc. Conceivably, there would be MoveUp and MoveDown methods too, but we didn’t bring it that far.

ie: alert(getTEST().getCount());

getTest() is created on the server side.

function getTEST() { return new ClientSideListbox(hiddenId, listboxId); }

Is that the proper way to write a webcontrol? I have no idea. But its clean and it works.

Advertisements

DvdFriend – Added the RECENT RELEASES section

February 24, 2008

For a site that would like to sell some DVDs, there certainly has been a lack of price listings.

In all 87 versions of the site that I’ve built to date, I’ve always struggled with determining what information to show where. Movies are coming out on DVD… should I list the date? The average rating? The Image? How many should I show? Should it be a number or by date range?

I refused to engage my self in those debates this time. Its showing 50 releases ranging from TODAY + 21 days, to whenever it runs out of movies. They’re listed on the right side of the main page. this will eventually evolve into a more detailed page. I’d like to add a mouse-over dialog to it (like netflix does). I can easily do that, but as always, making it pretty will be the difficult part.

A key thing I did tackle is determining which titles to show. There may be a ton of movies in there, but not all of them are interesting enough to put on the front page. So, its only showing DVDs of movies that someone on the site has reviewed. Additionaly, there’s a mechanism in there to flag movies that I want to show up. I haven’t added the page functionality yet, but the back end is ready to go.

Later, the date headings will become hyperlinks that will show all of the releases for that day, without any filters. At the rate I’m going though, I’ll spend 4 minutes doing it in 3 months.


DvdFriend – User Review Page

February 9, 2008

In the other blog, Chris requested a page that shows all of the reviews he has written. I estimated it would take 14 seconds. It took 50 minutes to build and deploy.

Example: http://www.dvdfriend.us/UserReviews.aspx?un=DVDFriend

1 – Create a page Called UserReviews.aspx. It uses the normal site master page. Loaded this page into the browser passing it ?un=DvdFriend. The page loaded without any content.

2 – Created a stored procedure called DFGetUserBlogs(@userName). There’s an existing view, DFvwBlogEntries, that already has all of the information I need. The stored procedure queries some fields for that where UserName=@userName

3 – Expose the query as a method in one of the business objects. There’s an existing class called BlogFactory that has lots of similar methods. I created the new method:

public static DataTable GetUserBlogs(string userName)
{
const string STORED_PROCEDURE = “DFGetUserBlogs”;
return AWDatabase.GetReadonly().ExecuteDataSet(STORED_PROCEDURE, userName).Tables[0];
}

4 – Added an object data source to the new page. Pointed it to the new method. Added a query string parameter for un.

5 – Added a datagrid to the page. Wired it up to the object data source. Loaded the page.. it’s good!

6 – Tweaked the grid. Shut off AUTO GENERATE COLUMNS, then added the fields in the order I wanted. Formatted the date field.

7 – Added the SortExpression for each of the columns.

8 – Added a server-side H1 to the page. Assigned its value from the UserName property, which returns the value of Request.Querystring[“un”];

9 – Changed Default.aspx and P.aspx: converted the USERNAME display fields to hyperlinks for the new page.

10 – Tested

11 – Deployed

Using out-of-the-box asp.net controls, and a little custom code, I was able to create this page pretty quick, but there’s a lot more that can be done.

The page is a dump of everything the user has. Currently, DVFriend is the worse-case-scenario, wich isn’t so bad. Once the list reaches a certain unknown size, it will be time to add paging.

Adding paging is easy enough; just have to enable it in the grid. However, the problem is that even if you’re only showing one page of data, it would still query for all of the rows. I wouldn’t be able to sleep if I did that (other than as, maybe, a transitional step).

I had a problem with the sort description. I set the Rating column to Rating,ProductName. I thought that would work, but it didn’t. It always sorted by rating ASC. DESC wouldn’t work, so I took off product name for now. The sorts will have to be revisited.

Other TO DOs
– I’d also like to add a summary grid and some filters on the top. IE: Pick a particular rating, and or date range; and/or reviews only. The summary would show how many ratings and reviews the user has, how long they have been active, etc.

– Show a list of other users on the right side so you can browser all reviews from all users. As long as the active user list is short, that will work… i can drop it in quick. Once the site conquers the world and the list is bigger, it won’t hold.

– Put the grid in an asp.net update panel. I would’ve done this to begin with if I thought of it at the time.

So, the asp.net controls are pretty powerful; quick and easy. I’m not a huge fan of the final result, though. Its super quick and easy, but each SORT operation is a post back. I’d rather use the querystring so that you can bookmark the page and get back to it in the same state you left it. As is, you can bookmark it, but it will go to its original state.

Prior to asp.net, I created a ton of reports in JTS that needed this functionality. It was all done by adding attributes to the html tags, and javascript would create the new url and request it. In the end, I ended up with one generic ASP page and a bunch of XLSTs and data sources. To sort, you’d add a SORT=”” attribute to the corresponding TH. There’d be some notation for asc, desc, etc (I forget the details). That worked out pretty well.


Pet Peeve 277: Ordinals vs key name

February 9, 2008

Pet Peeve #277

select FirstName, LastName, ShoeSize from MyFavoriteTable

… get a reader …

while (reader.Read()) {
Console.WriteLine(“First Name: ” + (string)reader[“firstname”]);
}

Per my previous post, I prefer (string)reader[“firstname”] over reader[“firstname”].ToString(). But it doesn’t stop there. Oh no. There’s more. I don’t like that approach at all. I’d much rather see reader.GetString(FIRST_NAME) but more on that later.

Notice that the select statement queries for FirstName, but we’re looking for “firstname” in our reader. The reader is going to do a case-sensitive search for “firstname”. When that fails, it will do a case-insensitive search. Why search twice? In fact, why search at all? Just tell it the index number.

string firstName = reader.GetString(0);
string lastName = reader.GetString(1);
Int32 shoeSize = reader.GetInt32(2);

That’s more efficient. Its going right to where it needs to go. But, 0, 1 and 2 aren’t very readable, are they? Gosh no.

I use constants.

const int FIRST_NAME = 0;
const int LAST_NAME = 1;
const int SHOE_SIZE = 2;

get reader… create while loop, etc

string firstName = reader.GetString(FIRST_NAME);
string lastName = reader.GetString(LAST_NAME);
Int32 shoeSize = reader.GetInt32(SHOE_SIZE);

Of course, for this to work the fields need to be in a known predictable order, which in my experience is usually the case. Maybe it wouldn’t fit if you have multiple existing procedures that return similar fields in different orders all going through the same method, but what are the odds of that? I’ve always been able to define the order.

Reader has another method called GetOrdinal() that you can use to look up the ordinals up front. So, if you really don’t know the ordinal, you can do this:

int firstNameOrdinal = reader.GetOrdinal(“FirstName”);
int lastNameOrdinal = reader.GetOrdinal(“LastName”);
while (reader.Read()) {
string firstName = reader.GetString(firstNameOrdinal);
string firstName = reader.GetString(lastNameOrdinal);

}

That way, you only do the lookups once rather than at every iteration of the loop.

If you know they’ll never change, then maybe you assign them to statics. I used to have a pattern to cover that scenario, but I quickly learned to dislike it because of the comingling of static/non-statics. Basically, when it got to the reader, it would check the value of one of the static ordinals. If it was -1, it would assign all of the statics. From there on, it wouldn’t have to do it again. I don’t like it.

The GetOrdinal follows the same steps as reader[“fieldName”]. First it does a case-insensitive search, then a case sensitive search if necessary. So, if you don’t have the ordinal, then hopefully you at least have the proper column name.

My position is clear: know the ordinals upfront and use them.

In order to do that effectively, you have to rely on the column order. If you do “select *”, that’s not reliable or portable. Always explicitly specify your select wether you’re hitting a table or view directly, or when a proc does it for you.

Reader Source Code

I took a look at the reader source code to compare reader[“string”] to reader.GetString(0). The by-ordinal approach doesn’t immediately return the value… it does a little legwork first. By-string does the same legwork, plus the additional work of looking up the ordinal to get it started.


Pet Peeve 318: (string) vs ToString()

February 9, 2008

I’m trying to make it a point to blog entries on a regular basis. I’ve been told its a good idea, and I like good ideas, but at this time of the day, I don’t have much to talk about.

So, I’m going to fall back to Jay Pet Peeve #318 (I just made that up, but I’ll start keeping track.)

Let me be upfront about Pet Peeve #318: It is superceded by Pet Peeve #277. If you have to do a lookup by string, then its covered by #277. However, I feel you should do the lookups by ordinal, which is covered by the next post.

ToString()

select FirstName, ShoeSize from SomeStupidTableThatDoesntActuallyExist

The name is a varchar, and the shoesize is an integer.

You write some code to execute the query, and you end up with a data reader. Then you start looping through the reader.

while (reader.Read()) {
Console.WriteLine(“First Name: ” + reader[“firstname”].ToString());
Console.WriteLine(“Shoe Size: ” + reader[“shoesize”].ToString());
}

An alternative for first name is:
Console.WriteLine(“First Name: ” + (string)reader[“firstname”]);

The first approach is technically sound, though I prefer the second simply for semantics. ToString() is used to provide the string representation of an object. When your object is already a string, you’re essentially saying “convert my string to a string”. Whereas, the 2nd approach says “here’s my string”. You wouldn’t do “hello”.ToString(), would you?

Does it matter? No. Its a pet peeve. I tried to be upfront about that. Pardon me if I wasn’t clear.

As for the “shoesize”, that’s a different story. That’s an integer, but you want to display it as a string, so ToString() doesn’t offend my delicate sensibilities in that case.

NOTE: I don’t like “firstname” and “lastname” in the reader calls either… that’s covered in the next post.


More string silliness

February 6, 2008

Lets convert that previous example into a C# 3.0 extension method.

It would be cool if we could add static extension methods instead.
IE: string.IsNullOrTrimmedEmpty();
rather than
x == null || x.IsTrimmedEmpty()

Test Class

using MbUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace YaddaYadda
{
class Program
{
public static bool IsNullOrTrimmedEmpty(string s)
{
return s == null || s.IsTrimmedEmpty();
}
static void Main(string[] args)
{
Assert.IsTrue(IsNullOrTrimmedEmpty(null));
Assert.IsTrue(IsNullOrTrimmedEmpty(“”));
Assert.IsTrue(IsNullOrTrimmedEmpty(” “));
Assert.IsFalse(IsNullOrTrimmedEmpty(” a “));
Assert.IsFalse(IsNullOrTrimmedEmpty(“a “));
Assert.IsFalse(IsNullOrTrimmedEmpty(” a”));
Assert.IsFalse(IsNullOrTrimmedEmpty(“a”));
Console.ReadLine();
}
}
}

The class with the extension method

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace YaddaYadda
{
public static class StringExtensions
{
public static bool IsTrimmedEmpty(this string s)
{
return s.Trim().Length == 0;
}

}
}


String silliness

February 6, 2008

Yesterday at work, Carlos and I had a silly converstation about the best way to check if a trimmed string is NULL or EMPTY.

I usually do this:

if (string.IsNullOrEmpty(x) x.Trim().Length == 0)

Carlos liked this better because its prettier:

if (x == null || x.Trim().Length == 0)

Of course, I took offense to that because I find my version to be quite attractive as well. So, instead of settling on which one is prettier, we broke it down into basic operations to see which one would be (theoretically) quicker.

In most cases, x will be a valid string.

string x = “123”;
if (string.IsNullOrEmpty(x) || x.Trim().Length == 0)

1 – Check for null
2 – Check for empty (.Length == 0)
3 – Trim the string
4 – Check the length
Under normal circumstances, there are 4 things to do

string x = “123”;
if (x == null || x.Trim().Length == 0)

1 – Check for null
2 – Trim
3 – Check the length
Under normal circumstances, there are 3 things to do.

So, the second choice is the better one, and we will use that going forward.

Out of curiosity, I took a look at the string source code to see how it was doing some things.

if (x.Trim().Length == 0)
or
if (x.Trim() == string.Empty)

.Length has been my means of choice. Its just a property value as opposed to a comparison. The string source code does it the same way.

System.String.IsNullOrEmpty()
if (value != null) { return value.Length == 0;} return true;

I think we’ve made a lot of progress towards solving some insignificant problems. Super.