Bia Securities

Author Archive

Have JQuery Autocomplete behave like Google Suggest

by Jim Geurts on Apr.12, 2010

For the current project I’m working on, I have a search box at the top of the site.  I wanted to add ajax suggestions (like Google Suggest) to it and thought it was a good exercise for the new JQuery UI 1.8 Autocomplete widget.  The autocomplete widget seems to be more suited for quickly searching pre-populated lists, but it offers nice extension points that allow you to easily customize its behavior.  Rather than having the control fetch a list of suggestions to just complete the word in the search box, I wanted the drop down to display a list of selections that when clicked, would navigate directly to the intended URL.

According to the documentation, you can pass in  three types of sources:

  1. A string representing a url that will return JSON data
  2. An array of strings (or objects)
  3. A callback that lets you define your own data

I’ll show you how to accomplish it using an array, but you can easily use one of the other methods as well.  If you want to use a different data source method, just make sure the result has the same object array signature as below.

1. Format the data source correctly

The autocomplete widget expects the source to be formatted as an array of strings or as an array of objects with specific properties.  Since we want to be able to assign a URL to each suggested item, we need to create an array of label/value pairs that represent each item:

var source = [{ label: 'My First Item', value: '/items/1' }, { label: 'My Second Item', value: '/items/2'}];

Now, when I pass that as the source to the autocomplete, we get items returned as a drop down menu.

2. Display the correct text for items

Unfortunately, when you hover over an item, it will replace the text in the search box with the value of the item:

To fix that behavior, the autocomplete widget allows you to hook into the focus event, that fires when an item in the drop down menu is hovered over/navigated to.  So we want to alter the behavior to use the label instead of value:

focus: function (event, ui) {
  $(event.target).val(ui.item.label);
  return false;
}

Now we get the label showing up in the textbox when we hover over an item:

3. Have the browser navigate to a URL when an item is selected

We’re almost there.  The last step that is needed is to wire up the item so that we navigate to the appropriate url when an item is selected:

select: function (event, ui) {
  $(event.target).val(ui.item.label);
  window.location = ui.item.value;
  return false;
}

A couple notes about what is happening in the select function. We set the value of the search box to the label value. This allows the control to auto complete in case the user gets anxious and wants to click Search before the browser redirects. After that, we instruct the browser to redirect to the selected item’s url.  Finally, we cancel the select event to make sure the default autocomplete behavior does not put the item value in the search box.

Putting it all together

We have the following:

$("#Query").autocomplete({
	source: [{ label: 'My First Item', value: '/items/1' }, { label: 'My Second Item', value: '/items/2'}]
	,focus: function (event, ui) {
		$(event.target).val(ui.item.label);
		return false;
	}
	,select: function (event, ui) {
		$(event.target).val(ui.item.label);
		window.location = ui.item.value;
		return false;
	}
});
Leave a Comment more...

UI Design – Part 2 of Rewriting a property management application

by Jim Geurts on Mar.27, 2010

One of the main areas that I am focusing on with the rewrite of Property Center is the user interface (UI).   My goal is to remove unnecessary complexity from the UI; not only making everyday tasks faster to accomplish but also reducing the learning curve for new users.  I’ve been using Balsamiq Mockups to quickly hash out ideas rather than spending valuable time doing it with html or some other slow process.

Updating the header

For example, I’ve been wanting to update the header to include a way for people to search and quickly get things done.

My first iteration looked like:

I still do like this layout because it puts everything in one row.  My main concern is that the drop downs would confuse people who are used to using links for user settings, logging out, etc.

With the next iteration, I moved search and quick actions to their own row:

I think this helps simplify the structure and it uses familiar links for user settings and logging in/out.

The last iteration improves on the previous by cleaning up some unnecessary labels, etc:

Overall, I think this is a good compromise of form and functionality.  It gives novice users a familiar experience with links for logging in/out as well as providing a more advanced UI for those who want to perform quick actions.

Leave a Comment more...

Rewriting a property management application – Overview

by Jim Geurts on Mar.02, 2010

This post begins a series of posts describing the process I am going through to rewrite and modernize my online property management application.  The end result will be relaunching the project as a rebranded application.

What are the goals?

There are many areas that need improvement from their current state.  A few of those areas are:

  • Database structure
  • UI Design and User Experience (UX) optimizations
    • Menu placement
    • Easier CMS user experience
    • Better tenant login experience
    • Ensure a consistent UI and use AJAX/popup dialogs in a controlled manner
  • Lacking features
    • Site search
    • Multiple payment processor options for tenant payment
    • REST based API
    • Data import/export
    • Postal mail options
    • Tenant credit screening
  • Performance tweaking
  • Strong documentation with page specific help
  • Pricing – I have exciting plans for revamping the pricing model.

What is the plan to get this done?

Technically speaking, the site will be upgraded to use Asp.Net 4, MVC2, NHibernate 3.x, StructureMap 3.x, and jQuery 1.4 running on Windows Server 2008 and Sql Server 2008.  I’ll be using Ayende’s fantastic NHProf tool to validate my NHibernate usage.

The database updates are nearly finished.  These updates include adding logical delete and other audit trail enhancements, full text search, and quite a few structural improvements which should translate directly to application speed improvements.  While designing an app from the model layer first is ideal, sometimes it leaves many desirable areas of improvement with relational db design and performance.  Upgrading an app is a different beast than starting from scratch, and I felt that the best place to start was to get the db in order.

I’ll soon begin to mock up the new user interface (UI), using Balsamq MockupsI’ve written about using Balsamiq in the past and still feel that it is one of the quickest and easiest ways to create useful wireframes.  I’ll add additional posts describing how I use Balsamiq to illustrate/prototype the major portions of the application.

1 Comment more...

Visit to Sweet Water Organics

by Jim Geurts on Jan.22, 2010

This past Wednesday, I had the pleasure of exploring a hidden gem in Milwaukee.  Sweet Water Organics is a local company based in Bay View that is commercializing Will Allen’s aquaponic system.  Aquaponics is the method of growing crops and fish together in a re-circulating system.  They had an “open house” and perch auction to not only generate some cash for the business but to also show off their digs.

Nicole and I hung out there for just under two hours.  Among others, we spoke with Jesse Hull, the man responsible for keeping Sweet Water operational and its lead horticulturist.  Not only did he blow me away with his knowledge of plants, our conversation ranged from the 5-second rule to using grow lights for heat transfer.  It was very stimulating to chat with him and I look forward to talking with him in the future.

Sweet Water Organics has great connections with many local chefs and restaurants.  They’re all about the miles to market mantra, where they try to provide local, sustainable ingredients.  They are looking to have retail operations in the future, that will allow you to buy fresh produce and fish directly from them.  Until that happens, you’ll be able to buy fish at upcoming auctions like the one they just had.

The vibe I felt while there was similar to how Lakefront Brewery felt in its early days.  There’s an excitement in the air and everyone knows that Sweet Water Organics is not only a benefit to the people but also the community.  Interesting to note, Lakefront provided a keg of delicious beer for all to enjoy while they mingled throughout the building.

Leave a Comment :, , more...

Localize Asp.Net MVC Views using a LocalizedViewEngine

by Jim Geurts on Jan.05, 2010

Localizing content is never an easy task.  Asp.Net tries to make localization and globalization easier with built in support for resources and cultures.  While I think that is a good start, I feel that the typical localization technique for asp.net applications is slightly misdirected and could be implemented easier.

In the past, I’ve put every sentence into a culture specific resource file.  Those sentences may be composite format strings or they could just be fragments.

This not only makes it difficult to rapidly develop, but can also create some rather difficult situations when special characters are introduced.  Think percent signs and currency symbols on Edit views.  Not to mention getting right-to-left languages like Arabic to display nicely.

A different approach

I propose a different solution to resource files that contain string fragments.  Rather than piecing together views with resource fragments, why not just have one view per language, per action.  Each language specific view can be identified by including culture names in their file name.

So if you have an Index action on the Home controller and want to support the default language (en-US) and Japanese (ja-JP), you would have the following files:

/Views/Home/Index.aspx
/Views/Home/Index.ja-JP.aspx

An added benefit to this method, is that it allows you to add new translations to your web application without requiring a recompile.  Along those lines, you can incrementally translate your site as budget and time allow.  If you haven’t added a translated view yet, the view engine will fall back on the default language view.

What are the downsides?

While this all sounds like a nice solution, there is one major downfall.  You duplicate the markup in many places.  So if or when you make a change in the future, you’ll have to go through each language specific view and make the change there as well.  That’s a lot to ask of a developer, but I feel that this method outweighs trying to piece together fragments and maintain text outside of the view.

How is this accomplished?

As everyone is aware, Asp.net MVC allows developers to extend the framework rather easily.  To allow for language specific views, we just need to tweak the WebFormViewEngine to first check for the view of the CurrentUICulture.  If that page is not found, let the view engine continue as it normally would.

public class LocalizedWebFormViewEngine : WebFormViewEngine
{
public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
{
string localizedPartialViewName = partialViewName;
if (!string.IsNullOrEmpty(partialViewName))
localizedPartialViewName += “.” + Thread.CurrentThread.CurrentUICulture.Name;

var result = base.FindPartialView(controllerContext, localizedPartialViewName, useCache);

if (result.View == null)
result = base.FindPartialView(controllerContext, partialViewName, useCache);

return result;
}

public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
string localizedViewName = viewName;
if (!string.IsNullOrEmpty(viewName))
localizedViewName += “.” + Thread.CurrentThread.CurrentUICulture.Name;

string localizedMasterName = masterName;
if (!string.IsNullOrEmpty(masterName))
localizedMasterName += “.” + Thread.CurrentThread.CurrentUICulture.Name;

var result = base.FindView(controllerContext, localizedViewName, localizedMasterName, useCache);

if (result.View == null)
result = base.FindView(controllerContext, viewName, masterName, useCache);

return result;
}
}

To specify that you would like to use the LocalizedViewEngine, modify the Application_Start method in your Global.asax.cs file to be similar to:

protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();

RegisterRoutes(RouteTable.Routes);

ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new LocalizedWebFormViewEngine());
}

That’s it.  I’m interested in hearing your thoughts about this method.

10 Comments more...

Get jQuery intellisense in VS.Net when using a CDN

by Jim Geurts on Oct.15, 2009

I recently heard about this technique to get jQuery intellisense working in Visual Studio .Net.  jQuery intellisense traditionally has never worked properly for me because I don’t use <head runat="server"> and thus don’t link to javascript files the MS way.  Most of the sites I build today just reference jQuery on a CDN like Google or Microsoft and that breaks Visual Studio’s ability to find the associated vsdoc.js file.  This works, however:

Add the following line to your master page file in the <head> element area under your normal jQuery script tag.

<% /* %><script type="text/javascript" src="http://ajax.microsoft.com/ajax/jQuery/jquery-1.3.2-vsdoc.js"></script><% */ %>

That line wraps the script tag in comments, so the script tag never gets rendered on the client side.  Visual Studio sees a valid file, though, and provides intellisense based off the comments in that file.  Below is a full example page layout:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Example jQuery Intellisense</title>
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script>
  <% /* %><script type="text/javascript" src="http://ajax.microsoft.com/ajax/jQuery/jquery-1.3.2-vsdoc.js"></script><% */ %>

  <!– Add your own javascript here –>

</head>
<body>
   Do work son
</body>
</html>

1 Comment more...

Balsamiq

by Jim Geurts on Aug.28, 2009

I heard about Balsamiq recently and figured I’d pass it along.  It’s a nice tool for quickly creating screen mockups.  After playing with it for about 5-10 minutes this morning, I’ve got to say that I’m impressed.  I definitely think it is a nice alternative to using powerpoint.  It is an Adobe AIR application which means that it’ll run on mac, windows, or linux.  They even allow you to test it out online, at their website.  Check it out!

Disclaimer: This is a bit of an ad, to get a free license, but look through this blog and show me where else I blog about other people’s products.  It’s not very often, so believe me when I say that this one is worth it!

Comments Off more...

IIS7: How to quickly and easily optimize your website using GZip compression

by Jim Geurts on Apr.21, 2009

DmbStream is starting to gain some momentum and I want the site to be received as fast as possible. It has over 1,100 registered users now, so every little optimization helps.  I used YSlow to pinpoint some of the major issues with the site and it really shed some light on the bottlenecks.

The first thing I did was use Google to host jQuery. This is an obvious win… The more sites that use Google to host their ajax libraries, the greater the possibility that the user will already have that library in their browser cache. Plus it offloads about 60k of javascript to Google’s CDN for each virgin request.

After that, YSlow said that javascript files were not getting gzip compressed. I have DmbStream hosted with IIS7, so things *should* be easy to configure. After reading this article, I added the following to the <system.webServer> element in my web.config file:

<staticcontent>
    <remove fileextension=".js" />
    <mimemap mimetype="text/javascript" fileextension=".js" />
</staticcontent>

Finally, the html output needed some compression. Once again, IIS7 makes this pretty simple to configure once you find the magic elements to add to the web.config. This article gives a good overview of the elements to add to web.config while this article describes using iis7 dynamic compression with output caching.

For my needs, I just added the following to the web.config <system.webServer> element:

<urlcompression dodynamiccompression="true"></urlcompression>

So what are the results?

Original:
Empty browser cache: 123.2K
Primed browser cache: 48.5K

Enabling gzip and dynamic content caching:
Empty browser cache: 80.3K
Primed browser cache: 9.5K

That’s a reduction in size of 35-80% per request. Port80 says that these improvements speed the site up 6.1 times. Not too bad, for just adding a few lines to the a web.config.

I have some other tweaks that I’ll continue playing with (it looks like .gif files aren’t being compressed), but by far the most useful compression came from turning dynamic compression on. In other terms, compressing the generated HTML output.

If you’re looking for some more reading material regarding IIS7 compression, I recommend checking out this post as well.

1 Comment more...

Helper to access route parameters

by Jim Geurts on Feb.21, 2009

I had a need to access routing information that was not readily accessible (as far as I could discover).  So, I wrote this helper to allow me to get the string, object pairs that Routing parses from the URL:

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Routing;

namespace BiaCreations.Helpers
{
    public class RouteHelper
    {
        private static IDictionary<string, object> _values;
        public static IDictionary<string, object> GetRouteInfo(HttpContext context)
        {
            if (_values == null)
            {
                HttpContextBase contextBase = new HttpContextWrapper(context);
                RouteData data = RouteTable.Routes.GetRouteData(contextBase);

                RequestContext requestContext = new RequestContext(contextBase, data);

                _values = requestContext.RouteData.Values;
            }
            return _values;
        }

        public static T GetRouteInfo<T>(HttpContext context, string key)
        {
            IDictionary<string, object> data = GetRouteInfo(context);

            if (data[key] == null)
                return default(T);

            object objValue = data[key];
            // It appears that route values are all strings, so convert the object to a string.
            if (typeof(T) == typeof(int))
            {
                objValue = int.Parse(data[key].ToString());
            }
            else if (typeof(T) == typeof(long))
            {
                objValue = long.Parse(data[key].ToString());
            }
            else if (typeof(T) == typeof(Guid))
            {
                objValue = new Guid(data[key].ToString());
            }
            return (T)objValue;
        }
    }
}

There are probably better ways to do this, but I needed this functionality and this works.  I am open to suggestions, though, if you have a better way of accomplishing this.  Oh, and my use case for needing this was that I needed value of the "id" parameter passed to a view, within an asp:substitution callback function.  I know that doesn’t completely follow the MVC philosophy, but you have to work with what you’re given, and sometimes it’s worth bending rules for the benefits that output caching can provide.

1 Comment more...

Migrate email from Gmail to Google Apps

by Jim Geurts on Feb.07, 2009

I, among others, have searched for a solution to transfer email in my gmail account to my google apps email.  There isn’t a formal way of doing so via Google, but low and behold I stumbled across a way to do it with Linux!  Consider this an addendum to that post, with complete instructions for those not familiar with linux.  I wanted to keep all of the labels, stars, read status, and email date.  As an added bonus, this method allows you to change the recipient value on emails so that it shows that it came from “me” rather than your gmail address.  I used Amazon EC2 to work the magic for me and 46k emails later, I’m a happy google apps user :)    You can just as easily use your own linux box alternatively.

This is how you can transfer your email from Gmail to your Google Apps email:

  1. Log into Amazon EC2 and select a Fedora instance.  It doesn’t really matter which instance you use.  I used “Basic Fedora Core 8 (AMI ID: ami-5647a33f)”
  2. Follow the example video on Amazon’s website for how to SSH into your instance
  3. Log in as root
  4. Install imapsync by running “yum install imapsync”
  5. Edit a script by running “nano run-imapsync”
  6. Paste in the following:
    imapsync –host1 imap.gmail.com \
    –port1 993 –user1 user@gmail.com \
    –passfile1 ./passfile1 –ssl1 \
    –host2 imap.gmail.com \
    –port2 993 –user2 user@domain.com \
    –passfile2 ./passfile2 –ssl2 \
    –syncinternaldates –split1 100 –split2 100 \
    –authmech1 LOGIN –authmech2 LOGIN \
    –justfolders

    imapsync –host1 imap.gmail.com \
    –port1 993 –user1 user@gmail.com \
    –passfile1 ./passfile1 –ssl1 \
    –host2 imap.gmail.com \
    –port2 993 –user2 user@domain.com \
    –passfile2 ./passfile2 –ssl2 \
    –syncinternaldates –split1 100 –split2 100 \
    –authmech1 LOGIN –authmech2 LOGIN \
    –regexmess ’s/Delivered-To: user\@gmail.com/Delivered-To: user\@domain.com/g’ \
    –regexmess ’s/<user\@gmail.com>/<user\@domain.com>/g’ \
    –regexmess ’s/Subject:(\s*)\n/Subject: (no–subject)$1\n/g’ \
    –regexmess ’s/Subject: ([Rr][Ee]):(\s*)\n/Subject: $1: (no–subject)$2\n/g’

    Replace name@gmail.com with your Gmail address and name@domain.com with your Google Apps email address

  7. Press Control-x to save the file and quit nano
  8. Make the script executable by running “chmod 744 run-imapsync”
  9. Create a file containing your Gmail password by running “nano passfile1″
  10. Type in your Gmail password and press Control-x to save the file
  11. Create a file containing your Google Apps password by running “nano passfile2″
  12. Type in your Google Apps password and press Control-x to save the file
  13. Execute the script by typing “./run-imapsync”

Depending on the size of your mailbox, you’ll have nirvana in a few hours :)   Transfering my 46k emails weighing in around 2.5Gb took roughly about a day… I had to babysit the process because it failed after a while for some unknown reason.  But restarting it with the specified –maxage param will get you right back near where you left off.  You may notice that I call imapsync twice in my script file.  It was failing on messages that had multiple labels and the folders weren’t created yet.  So the first call creates all of the folders while the second call moves all of the messages.

2 Comments more...

Looking for something?

Use the form below to search the site:

Still not finding what you're looking for? Drop a comment on a post or contact us so we can take care of it!

Visit our friends!

A few highly recommended friends...

Archives

All entries, chronologically...