Sunday, May 30, 2010

Big Design Conference 2010 Recap



Okay, at the start of the Big Design Conference, Susan Weinschenk (@thebrainlady), mentioned that we’ll hear different perspectives throughout the conference on UX and ideas in general. Well, I thought I would share my experience from a developer’s perspective. This is all from memory because I wrote very few notes, so hopefully I remembered the right things…although I know I didn’t on some of them. Plus I found out in the keynote on day one that my memory is not great. I couldn’t even remember CIAI BMF BIIP ODHP…of course I do now because she showed us this: CIA IBM FBI IPOD HP, which makes it a little easier.

Day 1:

  • Keynote by Susan Weinschenk, @thebrainlady
    What I took away…
    • Brain has three parts – new, mid, and old
      • new = language processing, speech, thinking thoughts, planning, etc
      • mid = emotions
      • old = danger, sex, and food
    • Our 5 senses take in about 11 million pieces of information every second and ONLY 40 of those are processing consciously.
    • Use usertesting.com - $39/person
    • We were asked to draw which way we would turn two knobs to get warm water. The results were all over the place on how people would turn the knobs. The idea behind the activity was to show that people have different models in their heads of how things should/do work. In this instance, I was curious as to how many people know the saying, “lefty loosy, righty tighty”. My guess is that the majority of people that said turn both to the left have heard that saying.
    • We were also asked to divide a circle into quadrants and label them A, B, C, D. I started in the top left and did A, B and then on the next “line”, C, D. After seeing the results of how people arranged the letters, it seemed to me that culture mattered more on how one would arrange them. My guess is that you’d arrange them in the manner you learned to read. I read from left to right and line by line, therefore, my result was A, B new line C, D.
    • I can’t draw a coffee cup with a saucer…At least not nearly as good as Dave, the marker artist sitting next to me.
  • The Age of the Brandividual: How to Build your Company and Personal Brand Leveraging Social Media by Mike D. Merrill, @mikedmerrill
    Download Slide Deck
    What I took away…
    • Update your LinkedIn account because it’s a great tool to manage connections.
    • Go ahead and reserve the username you want to use for your online activities on all the different sites, even if you don’t intend to use them. Especially places like Google, Facebook, and LinkedIn.
    • @aol.com email accounts means you’re over 40. Either buy yourself a domain or go with gmail.com.
    • Blog.
  • The How to Conduct Global UX Benchmarking by Alfonso de la Nuez
    What I took away…
    • I ended up leaving this one early because I thought it was going to be “More Than a Blue Button” by Ryan Merket. I did however spend the first 10 minutes trying to figure out if Alfonso had a ponytail because it looked like he did. I didn’t actually see that he does not have a ponytail until a later that day.
    • I didn’t make it to the Ryan’s session, I just walked around and waited for lunch.
    • My apologies Alfonso for leaving early.
  • Agile or Irrelevant? by Tom McCracken
    What I took away…
    • Tom likes talking about this stuff.
    • Tom likes Drupal.
    • NASA kinda formed the way we do projects today (waterfall).
    • Tom provided some visuals on waterfall vs agile methods, which were interesting.
    • Tom also mentioned the http://agilemanifesto.org/.
  • 10 Commandments of Social Interaction Design by Chris Pitre & Laura Verble
    What I took away…
    • Chris likes Beyonce and he’s going to buy a TV from VIZIO because they showed him a picture of one with Beyonce on it. (this was an example they provided)
    • Vagisil has a “Share with others” feature. (I hope this doesn’t become part of my Google search results)
    • Respect privacy.
  • Extreme Douchebaggery, formerly known as Extreme Social Marketing by Giovanni Gallucci, @gallucci
    What I took away…
    • It’s all about the money. Hence the douchebaggery :)
    • Use YouTube for marketing.
    • Fake Genuine.
    • Funny guy and to the point.

Day 2:

  • Keynote by Chris Bernard (only heard from lobby, sadly)
    • Heard some Ferris Bueller’s Day Off coming from the room and I wish I would’ve seen it.
  • When Data Gets Up Close & Personal by Stephen Anderson, @stephenanderson
    What I took away…
    • All about the Feedback Loop
    • Creator of the Mental Cards, which were handed out at the conference to help make it more social and connected. Of course, being shy it did not help me. Although I did trade with whoever asked me, but I didn’t approach anyone. I need to get over that probably especially if I want to try to start speaking at some of these events…
    • This was one of my favorite sessions. He made me think a lot about how my end-users are going to interact with my applications and how I can make it fun and enjoyable for them.
    • He provided a ton of examples and I’m hoping he releases his slide deck soon.
    • He mentioned dopplr.com, which I just signed up for and really like. In particular he mentioned the “Your Carbon” section, which shows you your carbon footprint in a kg CO2 number. He goes into detail on how they could improve that number. he suggests…
      • Make it visual…maybe tree icons
      • Make it relatable…show how I compare on average
      • Show me how I can improve…take a train, etc
      • Show me how I can help…link to places that plant trees
    • He also gave a small exercise to write down a project that we’re currently working on and list some of the business goals. Then he went into a section on his problem of not responding to email quick enough and some of the things that an email system could do to help him stay motivated. The idea was for us to think about our project while he was showing how he thought through his…I enjoyed the exercise
  • Designing with Lenses: Lessons from Other Design Crafts by Bill Scott, @billwscott
    What I took away…
    • This guy seemed really smart. Worked for Yahoo! & Netflix on the UX teams I believe.
    • His presentation was how to use “lenses” to design. He gave an example of simple/complex…as in playing Tic Tac Toe is too simple, but chess has simple rules, but is a very complex game.
    • I don’t really know how to explain this stuff…he basically went through some practices that were used to develop a board game called Pandemic. The game looked fun and the examples were great.
    • He also talked about working on certain UIs with Yahoo! & Netflix. Some worked really well, others not so much. I’m hoping he publishes his slide deck as well because I think that would help explain some of this stuff.
    • My favorite part of his presentation was probably the example of detecting what changed on an image after a page refresh and then seeing the difference without the page refresh. I think only 5 people saw the change with the page refresh and everyone saw it without the page refresh. Extremely enlightening when it comes to UX. He also gave a URL for samples of this, but since I didn’t write anything down, I don’t have it. Like I said before, hopefully he publishes the slide deck.
  • Effective Dashboard Design: Why Your Baby is Ugly by Aaron Hursman, @hursman
    Download Slide Deck
    What I took away…
    • Aaron’s presentation was basically a bunch of examples of what not to do and what to do when it comes to building a dashboard.
    • Luckily, he published his slide deck. I wouldn’t want to have to re-create all those samples.
  • Can Helen Keller Learn in the 21st Century by Sharron Rush
    What I took away…
    • There is a definite need for knowbility in K12 environment
    • Knowbility offers several training events throughout the year
      • John Slatin AccessU
      • AccessU West
      • ATSTAR (Assistive Technology Strategies, Tools, Accommodations, & Resources)
    • Knowbility is also involved with the community via the Accessibility Internet Rally (AIR), which raise public awareness about barrier-free technology.
    • iPhone is a universally designed product.
    • Advocate universal design over accessible design
    • 1 in 10 men are color blind
  • Behind the Kimono: A Peak Behind the Design Process by Russ Unger, @russu
    Download Slide Deck
    What I took away…
    • Russ was angry.
    • Russ wrote a book. A book that I tried to buy today at BN and they did not have it in stock. I’ll have to buy it on Amazon later.
    • Russ and a few of his friends are helping a non-profit called lend4health.
    • Russ likes wireframes.
    • Russ is extremely entertaining.
    • Russ is showing his work and not hiding it. He asked the question, why don’t we share our work. I immediately thought, “it’s scary”. He actually wasn’t talking about developers, but more the wireframes, but it still applies. Fear is what kept me from blogging and sharing for so long. You never know how someone is going to take what you post or if you’re practicing what you’re preaching or if you’re going to live up to whatever standards you or someone else has set for you…it’s tough, but well worth it. I’m really thankful for all the bloggers in our communities and all the commenters I get on this site.
    • Apparently I like just saying Russ…whatever.
  • Keynote by Jared Spool
    • Fun presentation.
    • Showed the different types of design styles.
      • Unintended Design
      • Self Design
      • Genius Design
      • Activity-Focused Design
      • Experience-Focused Design
    • All good designers know which design style they’re using.
    • Use a design style through the completion of a project, don’t mix and match.
    • Some different design decision samples:
    • The girl under tree rule for universities…he listed at least 10 universities with girls under or around trees on their homepage.
    • He gave examples of different types of design styles.
      • Self Design – 37 Signals
      • Activity-Focused Design – Six Flags
      • Experience-Focused Design – Disney World
      • Unintended Design – American Airlines error page
      • Genius Design – New City something or another maybe? Can’t Remember.

Books Bought Due to Conference:

Books I Intend to Buy Due to Conference:

Other things learned

  • SMU has an awesome quad.
  • There are a ton of community driven people in the UX area too.
  • Section 508 compliant isn’t enough to make your site a universal design.
  • BigD Conference gives out some awesome notebooks.
  • My wife can navigate and find her way around Dallas. I was impressed.
  • Don’t Tweet so much that your phone dies and you miss your wife’s text messages stating that she’s outside waiting and has been for the past 30 minutes.
  • Check out #bigd10

I think that’s it. I really enjoyed this conference and thought I’d share my experience. I’ll definitely be headed back next year! If you attended the conference and have posted your experience, please let me know and I’ll link to you.

Thanks for reading!

kick it on DotNetKicks.com

Tuesday, May 25, 2010

Creating a Fluent Interface with C#




So I’ve been using fluent interfaces for a while now and I’ve really grown to like them. It made me want to write one, so I did. I made a simple interface for creating an LDAP URL. It’s really basic, but I thought it was cool nonetheless. Here’s what I ended up doing…

I wrote a test of how I wanted it to read, which looked like this:

[Test]
public void LDAP_WithBaseDN_WithHost_Should_Return_Valid_URL()
{
var ldapurl = new LDAP()
.WithBaseDN(dc =>
"ad", dc => "derans", dc => "lab")
.WithHost(
"noc-dc01")
.URL;

Assert.That(ldapurl, Is.EqualTo("LDAP://noc-dc01/dc=ad,dc=derans,dc=lab"));
}

So what does this look like in inteface form…

    public interface ILDAP
{
ILDAP WithBaseDN(params Func<string, string>[] dnitems);
ILDAP WithHost(string hostName);
string URL { get; }
}

As you can see here, I’m using one of my favorite new methods to map/pass data, the good ole Func<string,string>. I could pass create the WithPort, WithScope, etc, but I wanted to keep this short and simple.

So here’s the actual implementation of ILDAP:

    public class LDAP : ILDAP
{
private const string _protocol = "LDAP://";
private string _ldap = _protocol;
private bool hasBaseDN;

public ILDAP WithBaseDN(params Func<string, string>[] dcitems)
{
hasBaseDN =
true;
_ldap += BuildBaseDN(dcitems);
return this;
}

public ILDAP WithHost(string hostName)
{
if (_ldap.Length > _protocol.Length)
_ldap = _protocol + hostName +
"/" + _ldap.Remove(0, _protocol.Length);
else
_ldap += hostName + "/";

return this;
}

public string URL
{
get
{
if (!hasBaseDN)
throw new InvalidOrMissingBaseDN();
return _ldap;
}
}

private static string BuildBaseDN(IEnumerable<Func<string, string>> dcitems)
{
var baseDN = "";
foreach (var dc in dcitems)
{
var dnitem = dc.Method.GetParameters()[0].Name;
if (!Enum.IsDefined(typeof(LdapOptions), dnitem))
throw new InvalidOrMissingBaseDN();

baseDN +=
"," + dnitem + "=" + dc.Invoke(null);
}
return baseDN.Remove(0, 1);
}
}

One thing about being fluent is that you don’t know which order someone is going to do something in. If someone knows how to handle this, that would be great. I suppose I could look in the NUnit code or fluent NHibernate, but haven’t really had a chance to do a whole lot of research into how to do this…so hopefully this is an okay start :)

So here’s what the LdapOptions looks like:

    internal enum LdapOptions
{
dc, cn, ou
}

Not a great name, but it works for now. Here are all the tests:

[TestFixture]
public class LDAP_Tests
{
[
Test]
public void LDAP_WithBaseDN_WithHost_Should_Return_Valid_URL()
{
var ldapurl = new LDAP()
.WithBaseDN(dc =>
"ad", dc => "derans", dc => "lab")
.WithHost(
"noc-dc01")
.URL;

Assert.That(ldapurl, Is.EqualTo("LDAP://noc-dc01/dc=ad,dc=derans,dc=lab"));
}

[
Test, ExpectedException(typeof(InvalidOrMissingBaseDN))]
public void WithBaseDN_Should_Throw_InvalidOrMissingBaseDN_If_BaseDN_Is_Invalid()
{
var ldap = new LDAP().WithBaseDN(dc => "ad", test => "test", dc => "derans", dc => "lab");
}

[
Test, ExpectedException(typeof(InvalidOrMissingBaseDN))]
public void URL_Should_Throw_InvalidOrMissingBaseDN_If_BaseDN_Is_Invalid()
{
var ldap = new LDAP().URL;
}

[
Test]
public void WithBaseDN_Should_Return_Valid_LDAP_URL()
{
var ldapurl = new LDAP().WithBaseDN(cn => "employees", dc => "ad", dc => "derans", dc => "lab").URL;
Assert.That(ldapurl, Is.EqualTo("LDAP://cn=employees,dc=ad,dc=derans,dc=lab"));
}

[
Test]
public void WithHost_And_WithBaseDN_Should_Return_Valid_LDAP_URL()
{
var ldapurl = new LDAP().WithHost("noc-dc01").WithBaseDN(cn => "employees", dc => "ad", dc => "derans", dc => "lab").URL;
Assert.That(ldapurl, Is.EqualTo("LDAP://noc-dc01/cn=employees,dc=ad,dc=derans,dc=lab"));
}
}

That’s it. Please let me know if you see ways to improve on the fluent goodness. Thanks for reading!


Shout it

kick it on DotNetKicks.com

Monday, May 17, 2010

Testing MVC 2.0 Controllers with Moq & NUnit




So I’ve been learning more and more about how to properly test my controllers in my MVC projects and I thought I’d share. I started using Moq recently and really like the syntax. It comes more natural to me over Rhino Mocks, which is what I was using before. I also started taking advantage of the MvcContrib test helpers, which are really nice! Of course I’m using NUnit, which seems to be what just about everyone uses. On a side note, testing is one of the major reasons to switch over to MVC from web forms. If you’re not using MVC now, I highly recommend switching over. You will not be disappointed.

Alright, well I’m going to be writing the tests for the contact form I blogged about back in March. Let’s get started.

First let’s test the Index() for the ContactController.

    [TestFixture]
public class ContactControllerTests
{
[
Test]
public void Index_Should_Return_Type_Of_ContactView()
{
//Arrange
var result = new ContactController();
//Act
result.Index();
//Assert
Assert.That(result.ViewData.Model.GetType(), Is.EqualTo(typeof(ContactView)));
}
}

The general rule of thumb for a unit test is Arrange, Act, & Assert. There generally should only be one assert per test, although I don’t think it’s set in stone. I’m sure some people disagree with that statement. Anyhow, this test is simple enough. Create a new instance of the controller, call the Index() action and assert that the returned model type is of ContactView. Done.

So I start on the next test and realize I’m going to need to use the SetUp attribute because I’m going to have some recurring items. Okay, so that looks like this:

        private ContactController _contactController;
[
SetUp]
public void Setup()
{
_contactController =
new ContactController();
}

So the second test looks like this:

       [Test]
public void Send_Should_Return_ContactView_If_ModelState_Has_Error()
{
_contactController.ModelState.AddModelError(
"testerror", "test error");
_contactController.Send(
new ContactView());

Assert.That(_contactController.ViewData.Model.GetType(), Is.EqualTo(typeof(ContactView)));
}

Basically, this adds an error and calls the Send() action and finally we assert that the ContactView is passed back. Here’s where we could add another Assert that checks the IsValid on the modelstate too, but I didn’t.

Next test looks like this:

        [Test]
public void Send_Should_Call_NotificationService_If_ModelState_IsValid()
{
var notificationService = new Mock<INotificationService>();
ObjectFactory.Inject(typeof(INotificationService), notificationService.Object);
_contactController.Send(
new ContactView());

notificationService.Verify(service => service.Notify());
}

So I use Mock<> to mock the notification service and then the StructureMap ObjectFactory to inject the mock in and then call the Send() action. Finally, I assert that Notify() was called on the mock.

Next I test to make sure the Success is passed in. Looks like this:

        [Test]
public void Send_Should_Call_NotificationService_If_ModelState_IsValid_And_Return_Success()
{
_contactController.Send(
new ContactView());

Assert.That(_contactController.ViewData["Success"], Is.True);
}

Then I test if the exception is thrown and it looks like this:

        [Test]
public void Send_Should_Call_NotificationService_And_Throw_A_NotificationException_If_Send_Fails()
{
_notificationService.Setup(service => service.Notify()).Throws(
new NotificationException());
_contactController.Send(
new ContactView());

Assert.That(_contactController.ModelState.IsValid, Is.False);
}

Notice I had to put the notification service in the SetUp too. Now my setup looks like this:

        private Mock<INotificationService> _notificationService;
private ContactController _contactController;
[
SetUp]
public void Setup()
{
_notificationService =
new Mock<INotificationService>();
ObjectFactory.Inject(typeof(INotificationService), _notificationService.Object);

_contactController =
new ContactController();
}

Finally I test to make sure everything comes back okay and the ContactView is passed back too. Looks like this:

        [Test]
public void Send_Should_Return_ContactView_If_No_Errors_Occur()
{
_contactController.Send(
new ContactView());

Assert.That(_contactController.ViewData.Model.GetType(), Is.EqualTo(typeof(ContactView)));
}

I also refactored the IsValid ModelState test to this:

        [Test]
public void Send_Should_Call_NotificationService_If_ModelState_IsValid()
{
_contactController.Send(
new ContactView());

_notificationService.Verify(service => service.Notify());
}

All our tests complete and green…

image

Thanks for reading!

Download the Source Here.


Shout it

kick it on DotNetKicks.com

Friday, May 07, 2010

Building a L2S Expression with .NET 3.5




I recently wanted to reduce some code redundancy by extracting out some code. We had a few helper methods that got results by specific fields.

For example:

public IList<HelpRequest> GetByLastName(string lastName)
{
return _dc.HelpRequest
.Where(x=>x.LastName.StartsWith(lastName))
.OrderBy(y => y.RequestDate).ToList();
}
public IList<HelpRequest> GetByFirstName(string firstName)
{
return _dc.HelpRequests
.Where(x => x.FirstName.StartsWith(firstName))
.OrderBy(y => y.RequestDate).ToList();
}

So obviously there is some redundant code here that we can get rid of and we did. First we created another helper method like this:

public IList<HelpRequest> GetByLastName(string search)
{
return GetBy(x => x.LastName.StartsWith(search));
}

public IList<HelpRequest> GetByFirstName(string search)
{
return GetBy(x => x.FirstName.StartsWith(search));
}

private IList<HelpRequest> GetBy(Expression<Func<HelpRequest, bool>> func)
{
return _dc.HelpRequests
.Where(func)
.OrderBy(y => y.RequestDate).ToList();
}

So that got rid of some of the duplication, but not all of it. We’re still doing a StartsWith in the helper and I didn’t want to do that logic there. So…now we have this and this took a long time to figure out…so I’m sure there is a better way because I basically got this working and stopped. Anyhow, here is the latest version, which has more lines of code, but less redundancy. I’m not sure which is better at this point…I’m thinking maybe the first refactoring, but this one is cool too and more flexible I think.

public IList<HelpRequest> GetByLastName(string search)
{
return GetBy(LastName => search);
}

public IList<HelpRequest> GetByFirstName(string search)
{
return GetBy(FirstName => search);
}

private IList<HelpRequest> GetBy(Func<string, string> func)
{
return _dc.HelpRequests
.Where(func.StartsWith<
HelpRequest>())
.OrderBy(y => y.RequestDate)
.ToList();
}

This one goes along with an extension method that looks like this: (Code below OUTDATED as of 5/9/2010 SEE BELOW)

public static Func<T, bool> StartsWith<T>(this Func<string, string> func)
{
var searchBy = func.Method.GetParameters()[0].Name;
var search = Expression.Constant(func(null), typeof(string));

var searchByParam = Expression.Parameter(typeof(T), searchBy);
var searchByProp = Expression.Property(searchByParam, searchBy);
var ignoreCase = Expression.Constant(StringComparison.CurrentCultureIgnoreCase);

var methodInfo = typeof(string).GetMethod("StartsWith", new[]{typeof(string), typeof(StringComparison)});
var containsExpression = Expression.Call(searchByProp, methodInfo, search, ignoreCase);

return Expression.Lambda<Func<T, bool>>(containsExpression, searchByParam).Compile();
}

So, as you can see this little extension method is kinda loaded down. I’m hoping there are some shortcuts to writing these expressions that I don’t know about…but I haven’t found any good examples. I browsed Google for a while and StackOverflow. I even submitted a question…I had one response, but it wasn’t exactly what I was looking for as you can see here. Anyhow, let’s go through this code.

First let’s talk about the GetByLastName & GetByFirstName methods…they’re REALLY simple now, but with one pretty big caveat I think. That caveat is that the property name isn’t strongly typed. If you follow my blog at all,  you know I’m a BIG fan of strongly typed things, but I haven’t found a better way in this instance. So, LastName & FirstName are properties on my HelpRequest object. I used this same method in the “Fun with Lambdas & HtmlHelpers” post. Alrighty, let’s keep going…so the GetBy(Func<string, string>) simply calls the func.StartsWith extension method and returns my IList…now to the meat of this post.

The extension method StartsWith<T> could be put into like 3 lines, but it’s way less readable when you do that…I basically set a variable for every different type of Expression object to keep it a bit readable. So the first thing I do is get my search by, which is my property name. On the next line, I create a constant, which is the right-side of the func passed in. Next on the list is the search by parameter, which sets the type to HelpRequest with the name of searchby (in this case, FirstName or LastName).  After that I set an Expression.Property for the searchBy and then I set another constant for the ignorecase that I’m going to pass into StartsWith in a bit.

Now, I find the method “StartsWith” on a string and setup the parameter definitions of what I want to pass in. In this case, a string and a StringComparison. After this, I call the startsWith on the search property with the search and ignoreCase parameters.

Finally, I return the lambda expression that is required for my Where on my LINQ to SQL statement. I also found that you have to call the Compile in order for it to work. What I do like about this extension method is that I can now use it for all my searches. Of course I’m going to continue testing it and verifying that there’s not a better way, but for now, I think it’ll work.

Please let me know if you have any suggestions or comments.

Thanks for reading!

UPDATE: 5/9/2010

So thanks to Brad commenting on using Ants or SQL Profiler, I did a little more investigating on the code above. Basically what was happening was this:

SELECT [t0].[FirstName], [t0].[LastName]
FROM [hd].[HelpRequest] AS [t0]

So, I made some adjustments to the static StartsWith<T> method and now it looks like this:

private static Expression<Func<T, bool>> StartsWith<T>(Func<string, string> func)
{
var searchBy = func.Method.GetParameters()[0].Name;
var search = Expression.Constant(func(null), typeof(string));

var searchByParam = Expression.Parameter(typeof(T), searchBy);
var searchByExp = Expression.Property(searchByParam, searchBy);
//var ignoreCase = Expression.Constant(StringComparison.CurrentCultureIgnoreCase);

var methodInfo = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });//, typeof(StringComparison)});
var containsExpression = Expression.Call(searchByExp, methodInfo, search);//, ignoreCase);

return Expression.Lambda<Func<T, bool>>(containsExpression, searchByParam);//.Compile();
}

First thing to note is the return type has been changed from Func<T, bool> to Expression<Func<T, bool>>. This allows us to do away with the Compile() at the bottom of the method. I was also able to do away with the ignoreCase and StringComparison stuff because it’s converted to SQL and in this case we’re not caring about case-sensitvitity. So now my SQL looks like this:

exec sp_executesql N'SELECT [t0].[FirstName], [t0].[LastName]
FROM [hd].[HelpRequest] AS [t0]
WHERE [t0].[FirstName] LIKE @p0',N'@p0 varchar(7)',@p0='deran %'

So, this is a good thing. Thank you Brad! Please let me know if you see anything else that could cause a problem.

Shout it

kick it on DotNetKicks.com

Wednesday, May 05, 2010

Character Count with jQuery and ASP.NET Web Forms




I recently needed to add a character count on several textareas on a Web app and decided to use a jQuery plugin. I know what you’re thinking, “I can’t believe Deran would use a jQuery plugin…I thought he hated jQuery”…or maybe you thought, “Of course you’re going to use a plugin…you always use a plugin”. Either way, I thought I’d share what I ended up doing.

So, basically, I have several <asp:textbox textmode=”multiline”/> objects on my page and I wanted a way to append a character count. Also you should note that my textboxes were embedded in a ContentPlaceHolder within a master page.

I ended up finding this jQuery plugin called NobleCount by The Product Guy (Jeremy Damian Horn). It’s a pretty nice plugin with several features and it plays nice with CSS. The only problem I found was that all his examples were for setting the character count on one textarea. I didn’t want to have all that redundant code, so I ended up writing something that loops through each textarea and finds the matching character count area. Remember I’m using the master page and .NET 3.5, so my ASP.NET ID won’t match the client ID. Here’s what I came up with:

$(document).ready(function () {

$(
'textarea').each(function () {
var id = $(this).attr("id");
var lastTwoCharInId = id.substring(id.length - 2, id.length);

$(
this).NobleCount('#count' + lastTwoCharInId, { on_negative: 'goRed', max_chars: 500 });
});

});

So what this says is…

  1. Loop through all textareas
  2. Get the id of the textarea
  3. Get the last two characters of the id (NOTE: I specifically named my ids so I could pull an identifier from them. I used two digits because it is HIGHLY unlikely that I’d have more than 99 textareas on the same page. Also, appending an identifier to the end of your textboxes allows jQuery to easily be able to take advantage of them with all the different selectors they have to offer.)
  4. Finally I call the NobleCount plugin and set my count span, the class of when the user goes into negative remaining, and I set the max characters.

You can also set max_chars to be $(this).attr(“maxlength), but that attribute is only rendered with textmode=SingleLine in ASP.NET. If you wanted to set different values, you could add a class with the max number and you’d have two selectors or set the max_chars to that class value. There are quite a few methods you could use.

This is why my HTML looks like:

    <asp:TextBox ID="tbQues01" runat="server" TextMode="MultiLine" MaxLength="500" Width="500px" />
<
div class="charbox"><span id="count01"></span> Characters Remaining</div>

My style looks like this:

.goRed {background: red; color: #fff}
.charbox {border: solid 1px #ccc; background: #e7e7e7; padding: 3px; width: 500px}

And of course this all generates these views:

image

image

I also wanted to note that Jeremy’s examples on the different settings are really good, so I would definitely check them out.

Thanks for reading!

Shout it

kick it on DotNetKicks.com

Related Posts Plugin for WordPress, Blogger...