Delay's Blog

Silverlight, WPF, Windows Phone, Web Platform, .NET, and more...

March, 2007

Posts
  • Delay's Blog

    AutoComplete++ [How to: Create a multi-word auto-complete text box]

    • 7 Comments

    By default, the AJAX Control Toolkit's AutoComplete extender doesn't have a notion of "words" and will try to auto-complete whatever text is currently in the text box, treating what's there as a single "word". One request that has come up a few times was for the ability to auto-complete multiple words individually. According to the comments of that work item, it looks like someone's made a set of proposed changes to do just that! It's great to have such an involved user community!! (Please note: The work item comments suggest those changes don't work in all browsers.)

    One thing I'd been meaning to do was write a quick sample of how to get reasonably good multiple-word auto-complete without making any modifications to the released AutoComplete extender. In other words, you can use the latest official Toolkit release (10301 in this case) and get some nice multi-word completion today. The key observation here is that the Web Service used to provide the list of candidate words has all the information it needs to do multi-word completion as well:

    Multi-word auto-complete example

    The complete code for the sample page is included below for anyone to look at or modify for their purposes. A few notes about the code:

    • The example was written to be simple, not efficient. The goal is to demonstrate the idea as plainly as possible, so there's no focus on performance.
    • The code works by auto-completing the last "word" of input and then populates the list of candidates with the resulting words and the preceding text.
    • All comparisons are case-insensitive so the user can type however they want.
    • In a real-world application, the list of candidate words would probably be retrieved from a helper function, a database, etc..

    Here's the complete ASPX file:

    <%@ Page Language="C#" %>

    <%
    @ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit"
        TagPrefix="ajaxToolkit" %>

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

    <
    script runat="server">
        [System.Web.Services.WebMethod]
        [System.Web.Script.Services.
    ScriptMethod]
        
    public static string[] GetCompletionList(string prefixText, int count)
        {
            
    // Fetch and sort the list of available completion words
            string[] allWords = "AJAX Control Toolkit AutoComplete auto automatic".Split(' ');
            
    Array.Sort(allWords);

            
    // Split input into completed words and prefix characters for the current word
            // Match on the current word and return candidate list including completed words
            // Ex: "he" -> "" and "he..."
            // Ex: "hello there th" -> "hello there " and "th..."
            string completedWords = "";
            
    string prefixChars = prefixText;
            
    int lastSpace = prefixText.LastIndexOf(' ');
            
    if (-1 != lastSpace)
            {
                completedWords = prefixText.Substring(0, lastSpace + 1);
                prefixChars = prefixText.Substring(lastSpace + 1);
            }

            
    // Create the completion list by searching for prefix matches
            System.Collections.Generic.List<string> completionList =
                
    new System.Collections.Generic.List<string>();
            
    foreach (string word in allWords)
            {
                
    if (word.ToUpperInvariant().StartsWith(prefixChars.ToUpperInvariant()))
                {
                    completionList.Add(
    string.Concat(completedWords, word));
                }
            }

            
    // Return the completion list
            return completionList.ToArray();
        }
    </script>

    <
    html xmlns="http://www.w3.org/1999/xhtml">
    <
    head runat="server">
        <title>Multi-Word Auto-Complete</title>
    </
    head>
    <
    body>
        <form id="form1" runat="server" onsubmit="return false;">
            <asp:ScriptManager ID="ScriptManager1" runat="server" />
            <asp:TextBox ID="TextBox1" runat="server" Width="300" />
            <ajaxToolkit:AutoCompleteExtender ID="AutoCompleteExtender1" runat="server"
                TargetControlID="TextBox1" ServiceMethod="GetCompletionList"
                MinimumPrefixLength="0" />
        </form>
    </
    body>
    </
    html>

    Enjoy!

  • Delay's Blog

    Lost (Sk)in Translation [AJAX Control Toolkit update!]

    • 7 Comments

    A short while ago we made available the 10301 release of the AJAX Control Toolkit. With this release, we managed to add some great core functionality, a couple of new controls, and a bunch of bug fixes for popular issues (as identified by our user community in the support forum and online issue tracker).

    The 10301 release includes two new controls:

    • ListSearch (by contributor Damian Mehers) - A handy enhancement to ListBox/DropDownList controls that allows the user to select items by typing the first few characters
    • SlideShow (by our own Kirti) - A visually pleasing slide show of automatically sized images and captions with optional automatic looping or manual control

    We also managed to add three pieces of core functionality that users have been asking for:

    • Localization support - The Toolkit now has an infrastructure for localizing the text in JavaScript files using the standard ASP.NET/AJAX support for resource files. There are placeholder files for the following 14 languages: Arabic, German, English, Spanish, French, Hebrew, Hindi, Italian, Japanese, Korean, Portuguese, Russian, Chinese (Simplified), and Chinese (Traditional). Complete localization of all strings hasn't been done yet, but our most popular request by far is for the string "Today:" at the bottom of the Calendar's popup - and that has already been localized to all of the above languages. Just set your system/browser's language settings to one of the supported languages and visit the Calendar sample page for a demonstration.
    • JavaScript comment/whitespace stripping - People are always interested in keeping the download size of their pages as low as possible, and the Toolkit goes the extra mile to help. When compiled in "Release" mode (as the official releases on CodePlex always are), all of the JavaScript files in the Toolkit automatically have all comments and unnecessary whitespace removed. Ted, the author of this functionality, was careful to adhere to the ECMA-262 ECMAScript Language Specification, so the modifications are both safe and effective.
    • ASP.NET Theme/Skin support - Central control is usually best, so it's nice to be able to use ASP.NET's theme/skin support to specify properties for controls across an entire web site in one central location. Toolkit controls didn't used to be skinnable (i.e., support the SkinID property) but now they are, so go ahead and start skinning your site. In fact, we used this new ability to style the CollapsiblePanels that we use for descriptions/properties/known issues on every sample page.

    And, with the help of our contributors, we fixed a bunch of bugs along the way...

    We hope you like the new release!!

    Recall that you can sample any of the controls right now (no install required). You can also browse the project web site, download the latest Toolkit, and start creating your own controls and/or contributing to the project!

    If you have any feedback, please share it with us on the support forum (or email me)!

  • Delay's Blog

    "You're gonna need a bigger boat." [A brief look at data storage requirements in today's world]

    • 2 Comments

    I've previously blogged about my data storage/backup strategy. Briefly, I've got one big drive in my home server that stores all the data my family cares about: mostly music, pictures, and videos (with a little bit of other stuff for good measure). To protect the data, I've got another equally big external drive that I connect occasionally and use for backups by simply mirroring the content of the internal drive.

    As things stand today, the internal drive is 320GB and the external drive is 300GB, but I've hit the wall and am almost out of space to add new files. Looking at hard drive prices these days, the sweet spot (measured in $/GB) seems to be with 500GB drives at about $140 (PATA or SATA). Any smaller than that and the delta from 300GB isn't enough to be interesting - any larger than that and the cost really goes up.

    I was already prepared to buy a new drive every year or so to allow for growth, so I was curious if getting a 500GB drive now would do the trick. I wrote a quick program to look at every file I backup and tally up the size according to the date the file was created. The C# program walks the whole directory tree, sums the sizes by date, and writes out a simple CSV file with the results. The idea here is to chart the rate at which I'm adding data in order to predict when I'd run out of space next. (Yes, it's easy to come up with more sophisticated heuristics, but this is really just a back-of-the-envelope calculation and doesn't need to be perfect to be meaningful.)

    Last night I opened the CSV file in Excel and charted the data. The resulting chart looks like this:

    Data Storage Space (GB)

    The blue line represents the cumulative size of the data I had at each point in time (horizontal axis) measured in GB (vertical axis) - you can see that I'm just above 300GB today. The red line is Excel's exponential trend line for the same data - it matches the blue line almost perfectly, so it seems pretty safe to say that my data storage needs are increasing exponentially. I was kind of afraid of that, because it means the 500GB drives I've been considering are likely to fill up within the next 8 months!

    Clearly, I need to be prepared to spend more on hard drives than I'd initially planned to - or else I'm going to need to significantly change how I do things. I've got some ideas I'm still considering, but charting this data was a good wake-up call that drive capacity isn't increasing as rapidly as I might like. :)

    I think that data storage and backup are issues that will affect all of us pretty soon (if they're not already). Backing up to DVDs doesn't scale well once you need more than 10 or so DVDs, and backing up over the network just doesn't seem practical when you're talking about numbers this large. Even ignoring the need to backup, simply storing all the data you have is rapidly becoming an issue. With downloadable HD movie/TV content becoming popular, high megapixel still/video cameras being commonplace, and fast Internet connections becoming the norm, it seems to me that content is outpacing storage right now.

    Here's hoping for a quantum leap in storage technology!

    Updated on 2007/03/14: I've just posted the source code for the program I wrote to gather this data.

  • Delay's Blog

    Computing the size of your boat [Sample code to help analyze storage space requirements]

    • 1 Comments

    Yesterday I mentioned a quick C# program I wrote to help analyze storage space requirements. There was some interest in how that program worked, so I'm posting the complete source code for anyone to use.

    using System;
    using System.Collections.Generic;
    using System.IO;

    class SizeOfFilesCreatedOnDate
    {
        
    private const string outputFileName = "SizeOfFilesCreatedOnDate.csv";

        
    static void Main(string[] args)
        {
            
    // Create a dictionary to hold the date/size pairs (sorted for subsequent output)
            SortedDictionary<DateTime, long> sizeOfFilesCreatedOnDate = new SortedDictionary<DateTime, long>();

            
    // Tally the contents of each specified directory
            // * If no command-line argument was given, default to the current directory
            if (0 == args.Length)
            {
                args =
    new string[] { Environment.CurrentDirectory };
            }
            
    foreach (string directory in args)
            {
                AddDirectoryContents(directory,
    ref sizeOfFilesCreatedOnDate);
            }

            
    // Output all date/size pairs to a CSV file in the current directory
            using (StreamWriter writer = File.CreateText(outputFileName))
            {
                writer.WriteLine(
    "Date,Size,Cumulative");
                
    long cumulative = 0;
                
    foreach (DateTime date in sizeOfFilesCreatedOnDate.Keys)
                {
                    
    long size = sizeOfFilesCreatedOnDate[date];
                    cumulative += size;
                    writer.WriteLine(
    "{0},{1},{2}", date.ToShortDateString(), size, cumulative);
                }
            }
            
    Console.WriteLine("Output: {0}", outputFileName);
        }

        
    private static void AddDirectoryContents(string directory, ref SortedDictionary<DateTime, long> sizeOfFilesCreatedOnDate)
        {
            
    // Display status
            Console.WriteLine("Scanning: {0}", directory);

            
    // Tally each child file in the parent directory
            foreach (string file in Directory.GetFiles(directory))
            {
                
    // Get a FileInfo for the file
                FileInfo fileInfo = new FileInfo(file);

                
    // Get the creation time of the file
                // * If last write < creation, then the file was moved at least once; use the earlier date
                // * The difference between local/UTC (~hours) is unimportant at this scale (~years); use local
                DateTime date = fileInfo.CreationTime.Date;
                
    if (fileInfo.LastWriteTime.Date < date)
                {
                    date = fileInfo.LastWriteTime.Date;
                }

                
    // Update the relevant date/size pair
                long size;
                
    if (!sizeOfFilesCreatedOnDate.TryGetValue(date, out size))
                {
                    size = 0;
                }
                sizeOfFilesCreatedOnDate[date] = size + fileInfo.Length;
            }

            
    // Recursively tally each child directory in the parent directory
            foreach (string childDirectory in Directory.GetDirectories(directory))
            {
                AddDirectoryContents(childDirectory,
    ref sizeOfFilesCreatedOnDate);
            }
        }
    }

    Notes:

    • I wrote this code for a simple one-time purpose, so there's no fancy/friendly user interface.
    • There's also no error-checking. In particular, if it bumps into a directory/file that it doesn't have permission to access, the resulting UnauthorizedAccessException will bubble up and terminate the process. (While this is unlikely to occur when using the program for its intended purpose of examining your data files, it is pretty likely to occur if playing around and pointing it at C:\.)
    • Other than adding comments and support for specifying multiple directories on the command-line, this is the same code I used to generate my chart.
    • The code for handling last write time being earlier than creation time was something I discovered a need for experimentally when I noticed that considering only creation time reported that none of my files were older than a couple of years. Apparently when I moved stuff around a couple of years ago, the copy to my current drive preserved the file's last write time, but reset its creation time (perhaps because of the FAT->NTFS transition).

    Enjoy!

  • Delay's Blog

    Toolkit talk two-fer! [Spoke at the ASP.NET Connections conference]

    Earlier today I presented two talks about the AJAX Control Toolkit at the ASP.NET Connections conference in Orlando, Florida: AMS305: ASP.NET AJAX Control Toolkit: See How to Take Advantage of the ASP.NET AJAX Control Toolkit and AMS304: ASP.NET AJAX Control Toolkit Unleashed: Creating Rich Client-Side Controls and Components.

    The content for these talks was based on stuff I've previously presented at TechReady4 in February and ASP.NET Connections in November of last year. However, this time around I was able to go into quite a bit more detail because I had over twice as much time to speak and do demos. The introductory AMS305 talk took the ImageFlix sample I've used before and expanded on it to demonstrate the Toolkit's new support for ASP.NET Skins/Themes and advanced animations followed by a quick walkthrough of what to do when things don't work like you'd expect (demonstrated here by adding a DropShadow to the popup panel). The more advanced AMS304 talk used an updated FontSize extender demonstration like the one that was demonstrated at the November ASP.NET Connections and included an overview of working with the Toolkit project, highlighting the automated testing framework and new localization support. Overall, there are about 15 completely new slides with fresh content, covering topics such as localization, automated testing, and more.

    I've attached the slide decks and the demo content for both talks to this post so that anyone who's interested can have a look at the slides or play around with the demos.

    I hope those of you who attended today enjoyed the talk and learned more about the Toolkit - it was great to have an opportunity to spend time with you!

Page 1 of 1 (5 items)