Data Files for Unit Tests

Recently I had an opportunity to try to explore a couple of different ways of putting data files into testing projects. Often you may have code that reads a config file, gets data out of a CSV, or modifies or reads some other file on disk. I have found that when considering the problem of getting the tests to run in a CI environment (TeamCity), using coding utilities that make testing a bit easier to run (ReSharper) and needing the test to work on other developers computers, that putting your data files as embedded resources is the way to go.

The alternative when using MsTest is something called the TestItemAttribute.  I first found this out on StackOverflow. Trying to use this methodology, I ran up against confusing and conflicting documentation and blogs regarding .testsettings that came in 2010 and still are somewhat compatible in 2012.  The replacement in 2012 is .runsettings which I also found had confusing documentation.  Many of the questions on these topics in StackOverflow are old enough to get people using them in 2010, with some trying to use the compatibilty in 2012.  After all this, the current version of Resharper (7.1.3) doesn't seem to be able to use the .runsettings so you end up with a test that works in the Visual Studio test runner but not Resharper's.   I didn't even get to the point of trying to get it to run in TeamCity.

properties.png

So back to embedded resources as a way of giving your unit tests something to work with.  The first step to this (after you have your testing project is to add the file(s) to that project.  Once you have added them, you need to change the Build Action property to Embedded Resource.  This basically copies the file into the DLL assembly that your project produces.

Now you need to get that resource available so your test can use it.  Generally you might be testing some other class that deals with files and you want to put a file into a temp location for the class under test to use it.  

class TestUtility
    {
        public static string WriteResourceToTempLocation(string resourceLocation,string directory)
        {
            System.Reflection.Assembly currAssembly = System.Reflection.Assembly.GetExecutingAssembly();

            Stream stream = currAssembly.GetManifestResourceStream(resourceLocation);

            var directoryPath = Path.Combine(Path.GetTempPath(), directory);

            Directory.CreateDirectory(directoryPath);
            var filePath = Path.Combine(directoryPath, resourceLocation);
            using (var fs = File.Create(filePath))
            {
                byte[] bytesInStream = new byte[stream.Length];
                stream.Read(bytesInStream, 0, bytesInStream.Length);
                fs.Write(bytesInStream, 0, bytesInStream.Length);
                fs.Flush();
            }
            GC.Collect();
            return filePath;
        }

        public static void CleanupTempLocation(string directory)
        {
            var directoryPath = Path.Combine(Path.GetTempPath(), directory);
            Directory.Delete(directoryPath, true);
        }
}

This class provides some helper methods that can be easily used in the [TestInitialize] and [TestCleanup] methods of your test class.

The last key to using this is being able to locate the resource in your dll.  The resource name and location will be appended to whatever the default namespace is as set in your project settings. This is generally what you named the project.

So if your project default namespace is "Foo.Bar.FileProcessing.Tests", and your testfile (testfile.txt) is located in a folder in your project named TestFiles.  then the resource location used in the above code would be "Foo.Bar.FileProcessing.Tests.TestFiles.testfile.txt"

My VS2012 Extensions

In visual studio there are a lot of good extensions that will simplify tasks and make tedious things a bit more friction free. 

Here are the extensions I currently have installed.

NameDescriptionComment
Activity DiagramPart of tangible t4 editor

Class DiagramsPart of tangible t4 editor
Code Review ThemeTheme Created by Visual Studio 2012 Color Theme EditorWith the Color theme editor you create and store color themes - you can later uninstall them using the extensions
Component DiagramPart of tangible t4 editor
Jeff Work ThemeTheme Created by Visual Studio 2012 Color Theme EditorWith the Color theme editor you create and store color themes - you can later uninstall them using the extensions
Microsoft Web Developer ToolsLatest Developer Tools For ASP.Net
Nunit Test AdapterLets Nunit Tests run in the built in test explorer
Persistence DiagramPart of tangible t4 editorI think this is an external install - but it gives you better highlighting for T4 templates
PowerCommands for Visual Studio 2010Made by Microsoft -

2012 version not out yet
http://visualstudiogallery.msdn.microsoft.com/e5f41ad9-4edc-4912-bca3-91147db95b99/

Productivity Power ToolsMicrosofthttp://visualstudiogallery.msdn.microsoft.com/3a96a4dc-ba9c-4589-92c5-640e07332afd
ResharperSeparate install - licensedThis doesn't show up in the extensions list.
State DiagramPart of tangible t4 editorI think this is an external install - but it gives you better highlighting for T4 templates
T4 editorThe t4 editor

http://t4-editor.tangible-engineering.com/T4-Editor-Visual-T4-Editing.html
I think this is an external install - but it gives you better highlighting for T4 templates

Use Case DiagramPart of tangible t4 editor
VersionOne TrackerIntegration with V1
Visual Studio 2012 Color Theme EditorLets you do a bunch of settings around the colors and theming of VS
Visual Studio Extensions for Windows Library Think this is added during install and lets you publish stuff to windows 8 store
VSCommands for Visual Studio 2012Squared Infinity
http://vscommands.squaredinfinity.com/features
This has some useful commands like setting up groups of cs files. But has caused some errors in Visual Studio - i uninstalled and re-installed to fix

Podcast List - November, 2011

My father just visited and wondered where I got the podcasts I listened to in the car.  I explained that I use DoggCatcher, an app on Android to download them directly from RSS feeds, but that with his iPhone, he should be able to get most of them via iTunes.

Below are a list of my favorite podcasts.   I am posting them in the order that Doggcatcher lists them.  I generally get in the car, tap the top one, NPR news and then continue to listen.  This generally is like my own private radio station, with pause.  I pick up on the story I was listening to where I left it.

Jeff's Podcasts, November, 2011

Podcast Pro/ Am Network/ Web Page Topic Length
(mins)
Freq. Comment
NPR: Hourly News Summary Podcast Pro NPR/PRI News 5-7  Hourly  
NPR Topics: Story of the Day Podcast Pro NPR/PRI News 10  Daily  
Sonnetoptics: The lions Road Am   SCA 30-60  occasional on hiatus
NPR: Planet Money Podcast Pro NPR/PRI Economics 15-25  2-3 per week  
Stack Exchange Am Self Tech / Programming / Community Building 60  weekly chatty
This Week In Google Pro TWiT Tech, Cloud 60  weekly  
Freakonomics Radio Pro self Economics 20-30 ? weekly  
this WEEK in TECH - MP3 Edition Pro TWiT Tech 120  weekly chatty
Current Geek Proish Frogpants /TWiT Geeky 5-7  weekly  
Judge John Hodgman Pro Max Fun Entertainment 20-30  weekly  
On the Media Pro NPR/PRI* News Media 60  weekly  
WNYC's Radiolab Pro NPR/PRI* Science 15 or 60  occasional  
This Developer's Life Am self Programming 60  occasional  
This American Life Pro NPR/PRI* Stories 60  weekly  
Back to Work Pro 5by5 Motivation/ Life Hacks 60  weekly chatty
The Moth Podcast Pro NPR/PRI* Stories 15-45  2/week ?  
NPR Topics: Technology Podcast Pro NPR/PRI Tech News 60  weekly  
Real Time with Bill Maher (mp3) Pro HBO News Opinion 60  weekly poor audio
Pennsic Independent Podcast Am Self SCA 30-60  occasional  
Chivalry Today Am Self SCAish 30-60  occasional  
Stuff You Should Know Pro Discovery Science/ Information 20-45  2/week  
All About Android Pro TWiT Tech / Android 60  weekly  
NPR Programs: Wait Wait... Don't Tell Me! Podcast Pro NPR/PRI News Quiz Comedy 60  weekly  

* A public radio station in some form... link takes you to website...

There are many more on my list but I rarely make it to them in a week.  With the change in driving that I have recently had - I doubt I will make it this far down the list on a regular basis.

Ads in Notification Bar in Android

I recently started to get some ads in the notification bar on my Nexus One.   It turns out it was from an app I don't remember installing called Gtalk Updater and that it was using a framework called Airpush.   I didn't like them one bit.  In addition to just being annoying by pushing to the notification bar, they also caused my phone to vibrate.

Luckily someone has created an App called Airpush Detector which will find apps that are causing this update and take you to the uninstall screen for them.

If you are noticing ads in your notification bar on your Android phone, you may want to check this app out.

Restoring SQL Express Databases

When I set up my latest computer, I paid special attention to making sure everything was backed up and I am really glad I did.  I had several issues with bad hard drives fairly early on and recently had another issue with some older drives that I have on the computer dying.  The only purpose these other drives had was to store the log files of my SQL Express Databases.   When they died, I actually didn't notice for several days until I tried to access one of these databases for some work on a development project.  All of my databases were offline.

Luckily I had set up an automated backup off all of the DBs.  Getting this setup using the Free SQL Express isn't obvious and requires (or at least helps to have) a great app that is out there called ExpressMaint.  This lets me run a Scheduled task that automatically backs up all my databases.   Those backups are then backed up by windows backup to a USB hard drive and by Carbonite online.

These databases were easy to restore after I changed the settings on my server to have the logs be on a different drive and made sure to restore to the good drives.   

Gmail's Priority Inbox (Categorized Important Messages)

I like Gmail's new "important" message feature that they are testing in beta currently.  To use it effectively with my setup that already automatically sorts my Gmail into categories, I had to do a few alterations.  I am using the "Multiple Inboxes" lab feature and I wanted to get the new division of "Unread and Important" and other "Unread".  The priority inbox only shows those emails that are in my inbox in those categories but because I have popfile moving my email to other tags, I needed to change the filters in my multiple inboxes.

I set the my filters to

  • is:unread is:important
    (show me unread items with the important flag) 
  • is:unread -is:important  
    (show me unread items that DON'T have the important flag)

So now my inbox has all my unread important messages at the top (sorted by category) and all my unread, probably not as important categorized emails in the next section.

 

 

Response to Twig 37 - Why users want apps.

I just finished listening to this week's TWIG. They were talking about how users prefer apps over web based applications.  It seems fairly obvious to me why this is but they didn't talk about it at all.  I think there are a few reasons that web based apps didn't take off.  

First is discoverability.  Apps in a marketplace or iTunes store are easy to find.  Sure, you could use "the Google" to find web based apps but finding web based applications that were specifically designed for a mobile phone was not an easy task against the background of the entire internet.   If google or apple had made an interface to help discover web based tools that worked well for mobile phones, then those apps would get discovered and used.  Even now, I know there are plenty of websites that use smart phone styles that would look good on my phone but finding them and knowing what they are is still difficult.

The next reason is that the network still sucks. I can't be in my office now without dropping off of 3g down to the edge network.  Leaving the city is a disaster for my data connection.  I think users would rather start an app, have limited functionality without network, or at least a friendlier error message indicating that the app can't connect to the server.  In many cases, the app doesn't need to be internet connected and so having a reliance on a terrible network would be bad for that app.   If the US had some decent wireless internet infrastructure, then web based apps would always be available and the need to be offline wouldn't exist as much.

One final reason I will talk about that apps became more popular than web pages is that the functionality of HTML5 was not as well known 2 years ago and even now I don't know how much access there is to phone based hardware systems.  Certainly any app that deals with local hardware would always be more preferable as an app than a web page.  Anything dealing with the camera, video, audio, file systems (ring tones, etc.) will need to be an app on the phone.  That being said, it would be nice to see some good functionality and libraries available for common tasks dealing with phone multimedia on the web server side.   Some common things like cropping a photo to a face, uploading pictures and video, should be very easy and hopefully as HTML 5 develops, this sort of thing will be more prevalent.  The phone browser can already detect location and use that.  The Gowalla web app is really very good, but without a presence in the marketplace, they ended up getting missed.

All this being said, although Google has certainly jumped on the app bandwagon, I don't think they are moving away from web based stuff.  They tend to have some of the better web interfaces for their applications such as gmail, google reader, calendar, etc.   I think they should get some sort of optimized search for phone web apps so that people searching for apps will see these web based options in the list.  In the long term, I do think it is better for developers and users to have web based applications available.  It brings instant interoperability making development easier across multiple platforms.  But until there is fast internet everywhere, decent toolsets for making web based apps, and a good search for them, this development will languish.

 

Transitioning to Java

So my work, in the wisdom of someone that is not me has decided that we should standardize the language and environments that we program in to Java and Eclipse.  I am sure you can already sense my excitement.  I'm not particularly afraid of the Java language or even think it will particularly hard to learn the language.   Its nearly identical to C# in syntax and even a lot of the framework is the same, I think.  I think my biggest transition will be the tools around programming.  This means going from Visual Studio to Eclipse.

I'm sure if you are reading this 2nd paragraph then you are at least somewhat interested in programming so I am going to drop all pretense of talking to someone who doesn't have a technical background.    Eclipse is the defacto open source standard development environment for Java.   I previously set up Eclipse on my work computer and that was pretty straight forward.  I like the "Add Software" feature that you can use to add plugins and other bits to the IDE.  I don't like just how complex the UI is.  We are using Maven which is software that helps manage your project by downloading any dependencies that your project may have.  Maven uses a file called pom.xml to manage your project and the repositories that it retrieves dependencies from.   Opening pom.xml in Eclipse gives a page of form fields that you can fill out.  I was able to figure out that this translated to the xml, but it was non-obvious to me how to actually see the XML.   I finally noticed a series of small tabs at the bottom of the window that related to other sections of the project and finally the XML itself in the last tab.

Luckily I am working on a project with a couple of experienced Java developers who will be there to guide me and set up some of the things on the project that I would just do the wrong way.  My first assignment was to take an XSD and use JAXB on it to create a series of classes for the project.  I found a Maven plugin that would run JAXB and create the classes.  One of my problems is that all the documentation surrounding setting up this program assumes I know what I am doing.  For instance, the 2nd instruction indicated I should edit my project file.  It took me a while to figure out that that was the pom.xml.  Couldn't they just say pom.xml?   Probably not, because everything is so flexible that it can probably be called something else.

Once I had set up the plugin, nothing was immediately apparent on what I should to to initiate the generation of my classes.  It turns out something was not set up right fairly deep in a settings dialog box (Java Build Path), causing the project not to compile right. Once I got that set, the files did generate automatically. 

There is a great deal to be said about how Visual Studio installs, checks for its dependencies, and has everything ready to roll when it is finally installed.   So far Eclipse and its associated plugins and servers make you jump through a lot of hoops to get things running right.  Everything is installed separately (I use "installed" loosely as more often than not, you just get a set of binaries that you put into a folder under your C: drive) and because you are installing these things in any particular place you have to let Eclipse and your system know where they all are.  Is it really that big a deal to have an installer that just puts stuff where it "should" go and then set all the settings correctly?

A Custom Ellipsis Plug-in for JQuery

I had a requirement to be able to show a long description for an item in a limited space.  The descriptions were coming from a 3rd party database and could be of any length.  The design called for the description area being two lines tall.  There is a CSS attribute called text-overflow: ellipsis.  It had several problems, however.  First of all it only worked on a single line basis. Mostly it had the big issue of not working at all in Firefox as it was a non-standard CSS call.

I found a jquery plugin to duplicate the functionality of the CSS call but like the CSS call it only worked on a single line.  My design spec also called for a More/Less link to be affixed to the block of text to expand it/ contract it.  The More/Less also had to support different cultures.

//ellipsis plugin http://devongovett.wordpress.com/2009/04/06/text-overflow-ellipsis-for-firefox-via-jquery/ + comments + custom mods
(function($) {
    $.fn.ellipsis = function(lines, enableUpdating, moreText, lessText) {
        return $(this).each(function() {
            var el = $(this);
            var resetDescription = function(height, originalText) {
            el.html(originalText);
            el.animate({ "height": height }, "normal", null, function() {
                el.ellipsis(true, true, moreText, lessText);
          });
            }

            if (el.css("overflow") == "hidden") {
 
                var originalText = el.html();
                var availWidth = el.width();
                var availHeight = el.height();
 
                var MoreLessTag;
                if (moreText) {
                    enableUpdating = true;
                    MoreLessTag = " <a class='MoreLessTag' href='#' >" + moreText + "</a>";
                }
                else MoreLessTag = "";
 
                var t = $(this.cloneNode(true))
                    .hide()
                    .css({
                        'position': 'absolute',
                        'overflow': 'visible',
                        'max-width': 'none',
                        'max-height': 'none'
                    });
                if (lines) t.css("height", "auto").width(availWidth);
                else t.css("width", "auto");
                el.after(t);
                t.append(" <a class='MoreLessTag' href='#' >" + lessText + "</a>");
 
                var fullHeight = t.height();
 
                var avail = (lines) ? availHeight : availWidth;
                var test = (lines) ? t.height() : t.width();
                var foundMin = false, foundMax = false;
                if (test > avail) {
                    //Binary search style trimming of the temp element to find its optimal size
                    var min = 0;
                    var max = originalText.length;
                    while (min <= max) {
                        var trimLocation = (min + max) / 2;
                        var text = originalText.substr(0, trimLocation);
                        t.html(text + "&hellip;" + MoreLessTag);
 
                        test = (lines) ? t.height() : t.width();
                        if (test > avail) {
                            if (foundMax)
                                foundMin = true;
 
                            max = trimLocation - 1;
                            if (min > max) {
                                //If we would be ending decrement the min and regenerate the text so we don't end with a
                                //slightly larger text than there is space for
                                trimLocation = (max + max - 2) / 2;
                                text = originalText.substr(0, trimLocation);
                                t.html(text + "&hellip;" + MoreLessTag);
                                break;
                            }
                        }
                        else if (test < avail) {
                            min = trimLocation + 1;
                        }
                        else {
                            if (foundMin && foundMax && ((max - min) / max < .2))
                                break;
                            foundMax = true;
                            min = trimLocation + 1;
                        }
                    }
                }
 
                el.html(t.html());
                t.remove();
 
 
                if (moreText) {
                    jQuery(".MoreLessTag", this).click(function(event) {
                        event.preventDefault();
                        el.html(originalText);
                        el.animate({ "height": fullHeight }, "normal", null, function() {
                        });
                        el.append(" <a class='MoreLessTag' href='#' >" + lessText + "</a>");
                        jQuery(".MoreLessTag", el).click(function(event) {
                            event.preventDefault();
                            resetDescription(availHeight, originalText);
 
                        });
                    });
                }
                else {
                    var replaceTags = new RegExp(/<\/?[^>]+>/gi);
                    el.attr("alt", originalText.replace(replaceTags, ''));
                    el.attr("title", originalText.replace(replaceTags, ''));
                }
 
                if (enableUpdating == true) {
                    var oldW = el.width();
                    var oldH = el.height();
                    el.one("resize", function() {
                        if (el.width() != oldW || (lines && el.height != oldH)) {
                            el.html(originalText);
                            el.ellipsis(lines, enableUpdating, moreText, lessText);
                        }
                    });
                }
            }
 
        });
    };
})(jQuery); 

The following features are added from the original:

  • More/Less link with expansion
  • multiple lines
  • title and alt text if no more/less text is provided

This hasn't been tested extensively under different conditions.

Things I would do if I had an infinite amount of time:

  • More Testing
  • Ability to override the More/Less text click event

Enjoy – I hope someone finds this useful.  This was my first foray into doing a jQuery plugin.  Even though a good chunk of the code was copied, I still learned quite a bit.

Today is my 40th Birthday.

Technically, its the 40th Anniversary of my birthday.  I figured I should write something down today so when I am reminiscing 80 years from now, I will remember how it felt to be so young.   If you aren't my future self reading this, well maybe it will give you a bit of insight into my life.  If you aren't interested in that, why are you here on my site? 

So, future Jeff, here's what's going on in my life:

I have been watching my calorie intake (food) and output (exercise) pretty consistently since August 27th.  I have lost around 10 lbs and am still going for 40-50 more.  (That's about 18- 22 kg more,  which I can only hope would be the normal way of measuring weight and mass 80 years from now).

I still love my android (made by a company called Google, remember them? ) phone about 10 months after getting the first model produced with that phone OS.  There is currently a bug in Google calendar that won't let me save events to my alternate calendars from the phone.  This bugs me.  Hopefully if the robot apocalypse was avoided, then computers are much smarter at helping us get through and organize our lives.  

Garret turns 10 tomorrow.  He will have a birthday party at Peter Piper Pizza on Sunday.  He has invited his fourth grade class to join him.  He is still learning to focus his attention on one thing at a time and hopefully has not only mastered this ability by the time you are reading this, but has excelled into the great man I know he will be one day.  

Gwen (and Garret and I) has just gotten over a cold and is back in school.  She is such an awesome little girl, bright and cheerful. I can't wait to see what she does with her sparkling personality and intelligence.

Leslie is still getting in to her new job. She is very excited about it and seems to love the new challenges it brings.  They seem to appreciate the work she does and that makes her happy.  I appreciate all the work she does with the kids and with me.

I have been walking since August, nearly every day and now some of my friends are encouraging me to sign up for a half marathon.  I have certainly walked just about that distance before during road marches in the army. Specifically the expert infantryman's test was 12 miles that had to be done in 3 hours with a "full" rucksack and uniform.  It seems like walking 13.3 miles in shorts, t-shirt and running/walking shoes should be no issue.

I'm still active in the SCA and still the Atenveldt Web Minister.  We just implemented a ticket system for helping us out with the tasks associated with that.  I need to enter in some of the old emails that are waiting for attention to the ticket system.  I am hoping someone will show some interest in taking this job over from me so I can spend some time exploring other things in the SCA.

This weekend I am going to a tournament to help out Master Yehudah's (Larry Baum's) family after his untimely death last month. I'm sure everything will work out well there.

Thats pretty much the news from here, on my 40th birthday.

Exercise Increase

For the last couple of weeks I have been focusing on tracking my calories, in and out.  Much of it has been with the help of a great app my wife found called Calorie Counter on the Android.    Calorie counter links to fatsecret.com which has a database of foods and exercise that you can enter in to keep track of your progress.  A great feature of the app on my G1 is that I can scan a barcode and more likely than not, the food is in the database already.  This makes it very easy to keep track of during the day.   Although I have been trying to eat balanced with not too much of anything, I am not trying a low carb diet at this point.  Just trying to keep the calories I am eating less than the calories my body is comsuming.  You can see my progress at the fatsecret site.  

Yesterday after walking my three miles in the morning (at about 3 mph). I was feeling pretty good and decided that today I would try to move up to jogging.  Although I was able to run/jog down the street, I felt a lot impact on my legs and on my body in general.  I decided that I would just try to increase the speed I was walking until I could lose more wieght and try to run again.   So I completed 2 miles this morning in just over 30 minutes and could definately feel some muscle pain/soreness in my calves shins.  So I will try to work on that speed for the next while until I feel pretty comfortable doing that for one hour or more.  

Squarespace featured on High Scalability Blog

The High Scalability Blog featured Squarespace recently and it was interesting overview of some of the technology that my site runs on.  I have to admit that I feel a bit guilty not running on Dotnetnuke or another Microsoft platform but I do like the ease of use of Squarespace.    I encountered Squarespace much the same way that Todd @ High Scalability did, through advertising on Twit.TV.  

My main wish is that Squarespace was a bit more extensible.  I'd like to add my own modules.  I'd probably even plug through Java to do it if I had to.  I'd really like to overhaul the Kingdom of Atenveldt Order of Precedence and put it on a platform that runs fast and is consistantly reliable.  This weekend I plan on spending some time on the site, upgrading to the latest version of DNN and maybe working on the skin a bit.  I have had a couple of designers show interest in helping me make a skin but nothing has ever come through with it. Maybe I can bring some more life back to the DNN based Atenveldt site with a bit of TLC. (tender loving care, not the learning channel... )

 

Rooted G1 For the Win

I rooted my G1 this weekend after being convinced by an article on Lifehacker.  I think it was a good decision.  I installed the latest Cyanogen Mod, which was just released this weekend.  I really like the new camera, the power widget and most of all the snappyness of the phone now.   Cyanogen says it uses more battery than the standard ROM, but honestly I feel like I was spending so much time waiting for the phone to respond to what I wanted it to do, I suspect that the real trade off in time is very little.

The thing that took the longest was backing up my 8GB SD card.  I also found the MyBackup app to be very handy in quickly getting all of my apps and shortcuts back quickly. (Although I did have to set each one back up... ).   DoggCatcher has a very handy back up capability but a non-obvious way of restoring.    I was able to short circuit the way described in the FAQ by using taskiller to kill the doggcatcher task and then the Lynda File Manager to delete the done.txt.

 

MotoRokr T505 + T-Mobile G1 = meh

I recently purchased a Motorola MOTOROKR T505 to enable bluetooth in my car (Honda Civic Hybrid).  I previously used a connector for my phone that let me connect power and a headphone jack to the car.  The audio went to an auxillary jack that was built  in to the car.  There are a couple of downsides for this.  There are a bunch of wires all over the place making the care look a little messy and when I talked to people with the power plugged in, they heard loud static and "screaching".

So when my Android phone got the new cupcake update that had stereo bluetooth, I wanted one of these fancy blue toot car sets that would let me just hop in my car, hit play on my phone, and head off down the road.  If only it had worked so well.  The first issue is that one of the things it says in the manual is that the T505 was not designed to stay in a hot car all day.  I don't know who didn't put that requirement in, but I already carry a big phone around, I'm not going carry around this garage door opener sized thing with me too.  It also ran on battery so that would eventually run out and I'd have to plug it in to the provided charger.  I just left the thing plugged in so I wouldn't have to worry about it running out of battery at some unfortunate time.  This mean that I really couldn't have it on my sunvisor though without having a power cord dangling around.   The next downside was pretty annoying, when I got in the car, it was a lot more than just hitting play on my phone and driving down the road.  I had to get in, turn on the T505, wait for it to turn on, hit the FM button, (meanwhile listening to my car spew static from the station the T505 is supposed to broadcast to), hope that I didn't accidently hit the FM button twice because then the T505 would go find another station to broadcast on, wait for the blue tooth to sync with my phone, then finally hit play and go.

This was all really too much for me.  I brought the T505 back to Best Buy and got a refund.  I will try out some other connectors for my wired solution that hopefully don't require me to unplug the power when I talk on the phone.

New Beginnings

Today was the start of some new stuff.  I started my new class at University of Phoenix.   Its pretty interesting so far.  I don't want to talk specifics too much but I am interested in it and hope it will be of some benefit for me.

I also woke up this morning at around 5, managed to drag myself out of bed and run around the block a couple of times.  Going around my block is just about exactly one half mile so I ran/walked a mile.  It took me about 18 minutes which obviously isn't great, but its a start and I plan on running about that long every morning, ramping up the distance as I get faster.   I asked Leslie to join me but its unclear if that will happen.  I also learned that taking a shower directly after running is kind of useless as I will continue to sweat after I get out of the shower.  So I will need to cool down a bit first tomorrow.  I also want to take advantage of all these new beginnings and changes of schedule to start fighting more.  I fought this weekend at the Highlands War, but it was the first time in a few months.

Altering the Look of the DNN FAQ Module

I had an issue with the way that the DNN module for FAQ's was behaving. Some extra space was appearing around my lists of questions and breaking up the way the page was supposed to look.  It turns out that the questions were getting these extra <p> tags around them.  Unfortunately, because the <p> tags were unexpected, the FAQ module was doing strange things like wrapping the <p> tags with <b> tags (it also wraps the whole thing in an <a> tag too to open up the question), which isn't really proper HTML. I was able to edit the template of the FAQ Module to stop the <b> tags but there was still the issue of the wierd spacing that was being created by the <p> tags.

The FAQ module was originally written against a different default rich text editor than what DNN ships nowadays (FCKeditor).  The old editor wouldn't add <p> tags to the text of your questions.  The FCK editor does and although it can be disabled by changing a config file, there are good reasons for keeping it, such as keeping your text wrapped correctly for proper HTML.  Using this config change would also change the behavior for the rich text editor through out all portals and modules, which would have bad side effects.

An option I have seen posted in a couple of places was to use the plain text module, but the problem with that is that changing that option seems to change all editor boxes sitewide, including the answer box.  Editing the source and leaving it without the <p> tags works but is not a good option because there are these special set of instructions for any editor who happens to want to update a question. This jumping through hoops isn't a good idea.

The solution is style sheets. Using Firebug, I could easily determine that the FAQ module wraps itself in a div with the class DNN_FAQsContent.  Now I can affect the <p> tags style within this div without affecting <p> tags throughout the site.  I decided to use the portal.css because I have several portals and I didn't want to unexpectedly affect them.  I was also pretty sure that I would want these effects even if I later changed skins.  So I didn't want to change the skin.css.

 

/* MODULE-SPECIFIC */
.DNN_FAQsContent a.SubHead p
{
    display:inline;
    margin: 0px 0px 0px 0px;
}

 

Let me explain what I did.  Since the specific <p> tags I wanted to attack were in the div with the class DNN_FAQsContent I started with that identifier.  The <p> tags were also (probably improperly) surrounded by an <a> tag with a class of SubHead.  This was all very easily discovered by using Firebug.  I wanted to make sure I wasn't affecting the <p> tags that are in the answers.   Once I used the CSS to identify the tags I wanted to change, I applied the changes.  display:inline changes the <p> tag from being a block element to being an inline element.  This is more along the lines of how the the FAQ module originally displayed the question.  I also took out any margins.

SCA Online OP Improvements, Link to Dynamic Forms Module - Part 2

In Part 1, I did all the background programming to put the settings in and display them the correct way in the settings page, but I haven't actually used the settings to create the link yet.  I also was blogging as I was programming which I now know why this is a rarity- It takes a long time and although the side trips down a buggy path may be interesting, its distracting from getting the actual work accomplished.  I finished this up yesterday without blogging about it and it went much faster.  So this is all done now and I probably won't linger too long on any of the problems I had.

The purpose of all the settings was to enable a link to be made from my Online OP module to the Datasprings Dynamic Forms module I have set up to take Award Reccommendations.   The first part was to customize this form a bit more so that it would even recognize this data.  The Dynamic Forms Module allows for data to come in in several ways, cookie, session, and querystring.   I decided to use the querystring to send this info forward.  In the advanced options of the question I found the setting I was looking for.

I set this for the Name and Branch field of the Awards Form and I also set up a hidden field for the internal member id.  I would use this to pass the id forward so it could link to the person in the OP.

Once the fields were set up, I modified the email result so that it could contain a link directly to the person that was being recommended.  The form completion events have 2 events, one to send an email to the crown, the other to send it to the person submitting a recommendation.  Both of these would recieve the link.   Each already had the main email set up so I only had to add the link at the bottom of the existing email text.  The main trick (if you can call it a trick) was to put the $(memid) field into the link target.  $(memid) was the hidden field I had set up to read a querystring I was sending.

Now to set up the Online OP module to provide the link.  The first thing I did was put the link in near the Awards header in the OP Record screen.

 

<h2 class="SCAMemberAwardsHeader">
        Awards Received</h2>
    <asp:Literal ID="litNoAwards" runat="server" Visible="false">No Awards Recorded.<br /></asp:Literal>
    <asp:HyperLink runat="server" ID="hlRecommend" CssClass="RecommendLink" >Recommend this person for an award.</asp:HyperLink>
    <div class="break"></div>
    <asp:Panel ID="pnlAwards" runat="server" Visible="true">
        <asp:DataGrid ID="dgAwards" runat="server" AutoGenerateColumns="False" CssClass="ResultList">

 

I also set up the link so it would float off to the right, under the picture of the person if there is one but inline with the Awards header.  This is from the module.css

 

.RecommendLink
{
    display:block;
    float:right;
    margin:15px 0px 15px 0;
}

 

 Next I had to generate the link.  I checked the settings to see if I should generate the link and then if I should, which tab should I direct to.  The querystring key's are determined by the short name of the fields in the Dynamic Forms module.

 

if(Convert.ToBoolean(Settings["LinkToRec"]))
            {
                hlRecommend.NavigateUrl = Globals.NavigateURL(Convert.ToInt32(Settings["AwardRecTabId"]), "",
                                                              "Recommended=" + h1MemberName.InnerText, "memid=" + memid, "branch=" + Residence);
            }
            else
            {
                hlRecommend.Visible = false;
            }

 

I initially had some trouble with the Friendly Url Provider I was using (iFinity Friendly Url Provider) and the way it was handling some special characters such as commas and single quotes (apostrophes), but this was resolved by using the latest dll provided on the site for DNN 5. 

Writing this blog after the fact is definately the way to go. Its like providng your own code review, which is very handy on a project you work on by yourself.

 

SCA Online OP Improvements, Link to Dynamic Forms Module - Part 1

A lot of coding blogs write about problems they have already solved and while I know its not unique to do so, I want to write down stuff as I do them for this project, rather than summarizing a problem I have already solved. Although this series of blogs involves the SCA, it will be more about the process or working on a DNN (DotNetNuke) module and the programming that goes into it.

If you are not familiar with the SCA Online OP, it is a module I wrote and maintain for the Kingdom of Atenveldt, a branch of the SCA.  The OP part of the module stands for Order of Precedence.  This is basically a ranking system that we use in the SCA loosely based on historical practices of knowing who is ranked above who.  I have taken that practice and put a lot of work around it to not only list the people and what awards they have in the right order, but enable viewing pictures of the people, descriptions of the awards, information about the local groups the people reside in and information about the members that reign over them.  All of this can be found on the Atenveldt Online OP.

Since I will have a lot of free time this memorial day weekend, I am putting some effort into some improvements I have wanted to do to the module for some time.  The first improvement I want to do is to link the OP to the new forms module I just installed for making an award recommendation for a person.  The forms module supports having a field get data out of a querystring, so it should be a simple task of creating a dyamic link that will point from a person's record in the OP to the award recommendation form.  Even though this module will never be in widespread use and will most likely be a single installation on the Atenveldt site, I try to add features that stay flexible and have the possibility of transferring over to anther DNN installation.

Step 1.  Settings.

I figure the module needs to be pointed to from the settings.  I am imaging a checkbox that indicates that we want to have a reference link to the award rec. form, along with a drop down of all the installed Dynamic Forms modules installed in the portal.

I have a settings page already created. So I will add the controls to the form.  Here is what the settings form looks like.

<%@ Control AutoEventWireup="false" CodeFile="SCAOnlineOPSettings.ascx.cs" Inherits="JeffMartin.DNN.Modules.SCAOnlineOP.SCAOnlineOPSettings"
    Language="c#" %>
<%@ Register TagPrefix="dnn" TagName="Label" Src="~/controls/LabelControl.ascx" %>
<table>
    <tr>
        <td valign="top" class="SettingsLabelColumn">
            <dnn:label id="dlBasePortal" runat="server" controlname="ddlPortalList" />
           </td><td valign="top">
            <asp:DropDownList runat="server" ID="ddlPortalList">
            </asp:DropDownList>
        </td>
    </tr>
    <tr>
        <td valign="top" class="SettingsLabelColumn">
            <dnn:Label ID="dlLinktoReccomendationForm" runat="server" ControlName="chkLinkToRec" />
        </td>
        <td valign="top">
            <asp:CheckBox ID="chkLinkToRec" runat="server" />
        </td>
    </tr>
    <tr>
        <td valign="top" class="SettingsLabelColumn">
            <dnn:Label ID="dlReccomendationForm" runat="server" ControlName="ddlDynamicForms" />
        </td>
        <td valign="top">
            <asp:DropDownList runat="server" ID="ddlDynamicForms">
            </asp:DropDownList>
        </td>
    </tr>
</table>
 

 

Now that the form fields are created, I will put some backing logic to them.  First I need to fill the drop down list with all the modules that are the DataSprings Dynamic Forms that I am looking for.  As I am writing this code, I realize that I want to put a message indicating that I can't detect the Dynamic Forms module being installed.   This will be convenient to test as I don't have the dynamic forms module installed yet on my local build installation.   As I look at the code a bit more, I decide to just hide the setting if I can't detect the module existing.  So I put a placeholder module around the two table rows that contain the info needed for setting up the dynamic forms stuff.

 

ModuleController mc = new ModuleController();
ModuleInfo mInfo = mc.GetModuleByDefinition(PortalId, "Dynamic Forms");
if (mInfo == null)
{
    phAwardRecsSettings.Visible = false;
}
else
{
    phAwardRecsSettings.Visible = true;
    ArrayList moduleTabs = mc.GetModuleTabs(mInfo.ModuleID);
    ddlDynamicForms.DataTextField = "ModuleTitle";
    ddlDynamicForms.DataValueField = "TabId";
    ddlDynamicForms.DataSource = moduleTabs;
    ddlDynamicForms.DataBind();
}

 

 You may notice I am programming in C#.  This is just something I would rather do than keep flipping between VB for this and C# at work.  The modules work perfectly fine. 

The next step for me is to try this out.  I don't like going to far without making sure this code works.  This keeps it fresh in my mind if I have to debug it rather than debugging something I did an hour ago if I wait that long before testing it out.  After testing that nothing was different when I didn't have the dynamic forms installed, I installed the dynamic forms module, imported my form from the live website, and tested again... It works!

 New Settings Display Correctly

Now that everything is displaying correctly - I need to actually make the settings save (and be read).  I want to make sure the drop down box shows the correct value when the settings are loaded and the check box is correctly checked or unchecked.  Here are the methods:

public override void LoadSettings()
       {
           try
           {
               if (!Page.IsPostBack)
               {
                   var basePortalId = ((string) Settings["BasePortal"]);
                   if (basePortalId == null)
                       ddlPortalList.SelectedValue = "0";
                   else
                       ddlPortalList.SelectedValue = basePortalId;
 
                   var awardRecTabId = ((string) Settings["AwardRecTabId"]);
                   if (awardRecTabId != null)
                       ddlPortalList.SelectedValue = awardRecTabId;
 
                   bool linkToRec = Convert.ToBoolean(Settings["LinkToRec"]);
                   chkLinkToRec.Checked = linkToRec;
               }
           }
           catch (Exception exc)
           {
               Exceptions.ProcessModuleLoadException(this, exc);
           }
       }
 
       public override void UpdateSettings()
       {
           try
           {
               var objModules = new ModuleController();
               objModules.UpdateModuleSetting(ModuleId, "BasePortal", ddlPortalList.SelectedValue);
               objModules.UpdateModuleSetting(ModuleId, "AwardRecTabId", ddlDynamicForms.SelectedValue);
               objModules.UpdateModuleSetting(ModuleId, "LinkToRec", chkLinkToRec.Checked.ToString());
 
               Response.Redirect(Globals.NavigateURL(), true);
           }
           catch (Exception exc)
           {
               Exceptions.ProcessModuleLoadException(this, exc);
           }
       }

When I go to test, doh... there is a problem.

DNN Critical ErrorLets see what the Event Log has to say about that. 

It turns out after quite a trip down the debugging rabbit hold that the above code doesn't compile in ASP.NET 2.0 (I may upgrade my site shortly).  The 'var' keyword got slipped in there by an overzealous ReSharper reformat.  After changing the settings in ReSharper, fixing the 'var', I found that the LoadSettings() actually gets called BEFORE the Page_Load... this means that I need to bind my controls in the LoadSettings().  Just because I am a bit paranoid about this changing in some future version, I put in a BindControls() method that I can call from Page_Load too.  I also protect for binding twice unnecessarily.  The final code for the settings is below.

using System;
using System.Collections;
using DotNetNuke.Common;
using DotNetNuke.Entities.Modules;
using DotNetNuke.Entities.Portals;
using DotNetNuke.Services.Exceptions;
 
namespace JeffMartin.DNN.Modules.SCAOnlineOP
{
    partial class ScaOnlineOpSettings : ModuleSettingsBase
    {
        #region Web Form Designer generated code
 
        protected override void OnInit(EventArgs e)
        {
            //
            // CODEGEN: This call is required by the ASP.NET Web Form Designer.
            //
            InitializeComponent();
            base.OnInit(e);
            this.Load += new System.EventHandler(this.Page_Load);
        }
 
        /// <summary>
        ///        Required method for Designer support - do not modify
        ///        the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
        }
 
        #endregion
 
        private void Page_Load(object sender, EventArgs e)
        {
            try
            {
                if (!Page.IsPostBack)
                {
                    BindControls();
                }
            }
            catch (Exception exc)
            {
                Exceptions.ProcessModuleLoadException(this, exc);
            }
        }
 
        private bool boundOnce;
 
        private void BindControls()
        {
            if (!boundOnce)
            {
                PortalController pc = new PortalController();
                ArrayList Portals = pc.GetPortals();
                ddlPortalList.DataTextField = "PortalName";
                ddlPortalList.DataValueField = "PortalID";
                ddlPortalList.DataSource = Portals;
                ddlPortalList.DataBind();
 
                ModuleController mc = new ModuleController();
                ModuleInfo mInfo = mc.GetModuleByDefinition(PortalId, "Dynamic Forms");
                if (mInfo == null)
                {
                    phAwardRecsSettings.Visible = false;
                }
                else
                {
                    phAwardRecsSettings.Visible = true;
                    ArrayList moduleTabs = mc.GetModuleTabs(mInfo.ModuleID);
                    ddlDynamicForms.DataTextField = "ModuleTitle";
                    ddlDynamicForms.DataValueField = "TabId";
                    ddlDynamicForms.DataSource = moduleTabs;
                    ddlDynamicForms.DataBind();
                }
                boundOnce = true;
            }
        }
 
 
        public override void LoadSettings()
        {
            try
            {
                if (!Page.IsPostBack)
                {
                    BindControls();
                    string basePortalId = ((string) Settings["BasePortal"]);
                    if (basePortalId == null)
                        ddlPortalList.SelectedValue = "0";
                    else
                        ddlPortalList.SelectedValue = basePortalId;
 
                    string awardRecTabId = ((string) Settings["AwardRecTabId"]);
                    if (awardRecTabId != null)
                        ddlPortalList.SelectedValue = awardRecTabId;
 
                    bool linkToRec = Convert.ToBoolean(Settings["LinkToRec"]);
                    chkLinkToRec.Checked = linkToRec;
                }
            }
            catch (Exception exc)
            {
                Exceptions.ProcessModuleLoadException(this, exc);
            }
        }
 
        public override void UpdateSettings()
        {
            try
            {
                ModuleController objModules = new ModuleController();
                objModules.UpdateModuleSetting(ModuleId, "BasePortal", ddlPortalList.SelectedValue);
                objModules.UpdateModuleSetting(ModuleId, "AwardRecTabId", ddlDynamicForms.SelectedValue);
                objModules.UpdateModuleSetting(ModuleId, "LinkToRec", chkLinkToRec.Checked.ToString());
 
                Response.Redirect(Globals.NavigateURL(), true);
            }
            catch (Exception exc)
            {
                Exceptions.ProcessModuleLoadException(this, exc);
            }
        }
    }
}

Now that the settings are in and saved, I actually have to make use of them in the module.   I will save that for Part 2.

Extracurricular Activites

Gwen and Garret both were participating in some extracurricular activities this year.  Gwen was interested in cheerleading and she had two friends that were going to a class put on by Young Champions of America. This video was her final showcase of the semester.  The next day she went to a cheerleading competition where her team took second place in state for YCA for Jr. Varsity Small group.  Photos of that event will be available on my Flickr account.

 

Gwen's Pom and Cheer Semester Showcase from Jeff Martin on Vimeo.

While this competition was going on, Garret was playing in the last game of the season in soccer.   He had been playing for several weeks with a team called the Red Dragons.  This was a non-competitive league run by the City of Peoria.  Garret showed himself to do pretty well in the goalie position and while there aren't any permanent positions on this team, Garret played goalie for half the game.

Garret's Last Soccer Game of the Season from Jeff Martin on Vimeo.

But now its summer and its hot.  The kids will be starting swim classes later this month.

Podcatching with Android

One of the things I love best about my T-Mobile G1 and Android is the application called DoggCatcher.  Doggcatcher is a podcatcher which means that it is able to subscibe to podcasts.   Podcasts are like radio shows but on the internet.  They can be about anything that anyone wants to put up.  In fact, some of the podcasts I listen to ARE radio shows.  Doggcatcher lets me set up podcasts in such a way that they basically are my own personal radio station... with Tivo.

Now I may not come from a background of a lot of knowledge of other ways that people listen to podcasts as this phone is really my first smart phone, but I am pretty happy with the results.

Doggcatcher gets set up with RSS feeds of podcasts.  I put the different feeds in order I like to hear them first.  My current feed list looks like this:

I have several more, but I rarely get down through the others to listen to them. I could pick any one of them randomly but I have a routine I am pretty happy with for my 45 minute commute to work.  I have a connector for my G1 that lets me charge it and hook the audio output into the auxilary input of my car.  So I listen to my podcasts with my car's speakers.  Each morning I connect the phone and download the hourly NPR news, this usually takes about 30-60 seconds.  Once I start listening, DoggCatcher will go down my list and any podcasts that have downloaded will play in the order  have them set.   So I get to listen to the ones I like the best first and  then down the line.   If I get a call, the podcast pauses and I can talk hands free, with my caller's voice on my speakers.  The podcast will pick up where it left off when I hang up.   Another feature Doggcatcher has (one that I asked for) is that when the phone no longer is under power, the podcast will pause.  This allows me to turn off my car and not worry that I am missing some of my podcast with the speakers off.  Its paused and ready to go again when I plug back in for the ride home.