Speeding up my Wi-Fi b/g network at home

I was trying to watch recorded HD content from my HTPC on my laptop, but it would stutter and be unwatchable. I quickly realised that my wireless network was too slow.

So to diagnose the problem I tried copying a large (2 Gb) file across my wireless network and see what sort of speeds I was getting. Only around 700Kb/second! Hmm, that seems awfully slow.

I opened up my router’s config page (Linksys WRT54GL using the patched tomato firmware) and tried the usual suspects – 1. changing to a different channel; 2. moving the router away from possible sources of interference; 3. trying a USB wireless network adaptor I had lying around instead of my laptop’s built in wireless, all to no avail. I was still only getting around 700Kb/sec.

I went into the router’s Advanced settings page and had a look at the options in there. Hmm, let’s try disabling this Afterburner setting. Boom! Once that was disabled the file copy went up to 2Mb/sec. That setting is disabled by default, but I had enabled it when I first setup my router (a year ago) thinking it would probably make things faster. Well that wasn’t the case, it was slowing things down.

I then tinkered with a few other settings, and I found that enabling Frame Burst sped things up a little bit, to around 2.5Mb/sec.

So yay, now I can watch recorded shows over my Wi-Fi g network! I don’t need to buy a 802.11n router and USB adaptor :-)

Moral of the story, which applies to all technology really – stick with the default settings to begin with, and once you know your baseline then tinker.

A member of the db_owner role must use… error in Visual Studio 2010

I keep getting this error message pop up in Visual Studio – “A member of the db_owner role must use the Visual Studio database diagramming functionality in order to set up the required database diagramming objects on the SQL Server.”

Typically it would happen whenever I drag a table from the Server Explorer onto the LINQ To Sql designer dbml. The error displays, and then after that the Server Explorer window locks up with the Windows 7 hourglass aka the toilet bowl. I can keep using Visual Studio but I can’t do anything in Server Explorer so I eventually have to restart Visual Studio 2010.

I was a bit confused because 1. I’m a dbo, and 2. I’d already setup diagramming in SQL Server Management Studio 2008.

I eventually realised – the solution I’m working on has two DB connections in Server Explorer – my application’s main db for settings etc (which is the one I’m working with when the error is thrown), and another read-only external database which my application reads data from. That external db doesn’t have diagramming set up yet so I presume VS is complaining about that.

There are two workarounds – 1. ask a dbo to set up diagramming on the other db, or 2. remove the connection to the other db from Server Explorer.

I did option 2, and the downside is I have to re-add the connection whenever I want to update my dbml. Fortunately I don’t have to do that very often.

Using jsTree to get a Treeview with checkboxes in ASP.NET MVC

UPDATE 17 December 2011!

After one year this is still the most popular post on my blog. However, this uses jsTree v0.9.9a2 and jsTree is now in version v1.0 which has changed completely. This post does not work with jsTree v1.0!!!

I have written a post using the latest version of jsTree here

jsTree is a jQuery plugin for creating a treeviews, and jsTree’s checkbox plugin allows you to create a treeview with tri-state checkboxes, like so:

Notice how “Origination” appears half-checked because only some of its children are checked.

Getting started

For this demo I am using ASP.NET MVC 2 and jsTree v0.9.9a2. Let’s start with a new “ASP.NET MVC 2 Web Application”, name it jsTreeDemo, and add the required jsTree files to our solution:

In the View, create a div which you want to become a treeview. I’ll name mine demoTree. Also add references to the required jQuery and jsTree scripts:

Views/Home/Index.aspx

<asp:content id="Content2" runat="server" contentplaceholderid="MainContent">
 <h2><%: ViewData["Message"] %></h2>
 <div id="demoTree">
 </div>
 <script src="../../Scripts/jquery-1.4.1.js" type="text/javascript"></script>
 <script src="../../Scripts/jquery.tree.js" type="text/javascript"></script>
 <script src="../../Scripts/plugins/jquery.tree.checkbox.js" type="text/javascript"></script>
</asp:content>

Next, create index.js, add a reference to it in Index.aspx, and add the following code:

Scripts/index.js

/// <reference path="http://ajax.microsoft.com/ajax/jQuery/jquery-1.4.1-vsdoc.js"/>

$(function () {
  $("#demoTree").tree({
   ui: {
     theme_name: "checkbox"
   },
   data: {
     type: "json",
     opts: {
       static: [
           {
              data: "Origination",
              children: [
                { data: "New Connection" },
                { data: "Disconnection" },
                { data: "Load Change" },
                { data: "Corporate" },
              ]
            },
            {
              data: "Confirm Application"
            }
       ]
     }
   },
   plugins: {
     checkbox: {}
   }
  });
});

This should create a basic treeview with checkboxes, with static data. Not very interesting yet.

Populating the tree with an AJAX request

Let’s create an ActionMethod to return some JSON data and use that data to populate our treeview. The jsTree documentation specifies how the JSON data should look but to help clarify, here’s an example:

[{"data":"Origination","attributes":{"id":"10"}",children":[
                         {"data":"New Connection","attributes":{"id":"11"}},
                         {"data":"Disconnection","attributes":{"id":"12"}},
                         {"data":"Load Change","attributes":{"id":"13"}},
                         {"data":"Corporate","attributes":{"id":"14"}}
                         ]},
{"data":"Confirm Application","attributes":{"id":"20"}}
...]

While it might be possible to write a LINQ query with anonymous types to return this data, it quickly gets rather hairy, so to simplify things let’s create a model to help us construct the data correctly. In the Models folder, add the following classes:

Models/JsTreeModel.cs

namespace jsTreeDemo.Models
{
 public class JsTreeModel
 {
   public string data;
   public JsTreeAttribute attributes;
   public JsTreeModel[] children;
 }

 public class JsTreeAttribute
 {
   public string id;
   public bool selected;
 }
}

In Controllers/HomeController.cs add the following function:

Controllers/HomeController.cs

[AcceptVerbs(HttpVerbs.Post)]
public JsonResult GetTreeData()
{
 var tree = new JsTreeModel[]
 {
   new JsTreeModel {
     data = "Origination",
     attributes = new JsTreeAttribute  { id="10"},
     children = new JsTreeModel[]
     {
       new JsTreeModel { data = "New Connection", attributes = new JsTreeAttribute { id="11"} },
       new JsTreeModel { data = "Disconnection", attributes = new JsTreeAttribute { id="12", selected=true } },
       new JsTreeModel { data = "Load Change", attributes = new JsTreeAttribute { id="13"} },
       new JsTreeModel { data = "Corporate", attributes = new JsTreeAttribute { id="14", selected=true} },
     }
   },
   new JsTreeModel {
     data = "Confirm Application",
     attributes = new JsTreeAttribute { id="20" }
   },
   new JsTreeModel {
     data = "Things",
     attributes = new JsTreeAttribute  { id="30", selected=true },
     children = new JsTreeModel[]
     {
       new JsTreeModel { data = "Thing 1", attributes = new JsTreeAttribute { id="31"} },
       new JsTreeModel { data = "Thing 2", attributes = new JsTreeAttribute { id="32"} },
       new JsTreeModel { data = "Thing 3", attributes = new JsTreeAttribute { id="33"} },
       new JsTreeModel { data = "Thing 4", attributes = new JsTreeAttribute { id="34"} },
     }
   },
   new JsTreeModel {
     data = "Colors",
     attributes = new JsTreeAttribute  { id="40"},
     children = new JsTreeModel[]
     {
       new JsTreeModel { data = "Red", attributes = new JsTreeAttribute { id="41"} },
       new JsTreeModel { data = "Green", attributes = new JsTreeAttribute { id="42"} },
       new JsTreeModel { data = "Blue", attributes = new JsTreeAttribute { id="43"} },
       new JsTreeModel { data = "Yellow", attributes = new JsTreeAttribute { id="44"} },
     }
   }
 };

 return Json(tree);
}

Now we need to change our javascript to tell it to use GetTreeData:

Scripts/index.js


  $("#demoTree").tree({
   ui: {
     theme_name: "checkbox"
   },
   data: {
     type: "json",
     opts: {
         method: "POST",
         url: "/Home/GetTreeData"
     }
   },
...

Now our tree should be populated with the same data as Figure 1.

Determining which items are checked when posting

Let’s put our tree inside a <form> and submit it.

Views/Home/Index.aspx

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
 <h2><%: ViewData["Message"] %></h2>

 <% using (Html.BeginForm("Submit", "Home", FormMethod.Post, new { id = "frmTree" }))
 { %>
   <div id="demoTree" style="height:300px">
   </div>
   <div>
     <input type="submit" value="Submit" id="btnSubmit" />
   </div>
 <% } %>

 <script src="../../Scripts/jquery-1.4.1.js" type="text/javascript"></script>
 <script src="../../Scripts/jquery.tree.js" type="text/javascript"></script>
 <script src="../../Scripts/plugins/jquery.tree.checkbox.js" type="text/javascript"></script>
 <script src="../../Scripts/index.js" type="text/javascript"></script>

</asp:Content>

And now let’s add the Submit method to the HomeController

Controllers/HomeController.cs

[AcceptVerbs(HttpVerbs.Post)]
 public ActionResult Submit(FormCollection form)
 {
   return View(form);
 }

and the View

Views/Home/Submit.aspx

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<FormCollection>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
 Submit
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

 <h2>Submitted</h2>
 You chose:
 <% foreach (var item in Model)
    { %>
      <%: item %>
 <% } %>
</asp:Content>

If you press the Submit button, nothing will happen as nothing is passed through in the FormCollection to Submit() in HomeController. Despite appearances, the jsTree doesn’t actually render any HTML <input>s for the checkboxes. So we need to write some javascript to call jsTree’s get_checked() function to figure out which nodes are checked, and then add them to the form somehow. One way to add stuff to the form is to generate a hidden field, since hidden fields within a form are posted. This code generates hidden <input>s with the same name as each of the checked items, and adds them to the form.

Scripts/index.js

function generateHiddenFieldsForTree(treeId) {
  $.tree.plugins.checkbox.get_checked($.tree.reference("#" + treeId)).each(function () {
    var checkedId = this.id;
    $("<input>").attr("type", "hidden").attr("name", checkedId).val("on").appendTo("#" + treeId);
  });
}

and bind it to the form’s submit event:

Scripts/index.js

$(function () {
  $("#frmTree").submit(function () { generateHiddenFieldsForTree("demoTree"); });
}

Now when we submit our form, we should see the ids of the values which were checked:

What about telling the tree to pre-check some items?

So how do we render the tree with some items already checked? My colleague Paul came up with this problem and the solution. Notice how we added “public bool selected” to JsTreeAttribute? This doesn’t do anything as far as the checkboxes are concerned, but it does add a custom property called “selected” to each node’s <li>. We can use that to tell the jsTree to check the given node, by using the jsTree’s callback option.

Scripts/index.js


$("#demoTree").tree({
   ui: {
     theme_name: "checkbox"
   },
   data: {
     type: "json",
     opts: {
         method: "POST",
         url: "/Home/GetTreeData"
     }
   },
   plugins: {
     checkbox: {}
   },
   callback: {
     onload: function (tree) {
       $('li[selected=true]').each(function () {
         $.tree.plugins.checkbox.check(this);
       });
     }
   }
  });

You’ll see that the nodes that are marked with selected=true in GetTreeData() are now checked when you load the page.

Here’s a link to the solution (VS 2010) with code samples.

UPDATE 17 December 2011!

After one year this is still the most popular post on my blog. However, this uses jsTree v0.9.9a2 and jsTree is now in version v1.0 which has changed completely. This post does not work with jsTree v1.0!!!

I have written a post using the latest version of jsTree here

“You cannot write updates to the target” in Visual Studio 2010

First, the good news. Data dude is now included in Visual Studio 2010.

If you get a message “You cannot write updates to the target when you compare the specified types of schema models” when you try and import your database into a data dude project, it’s because you’ve done a schema compare of your database against a “SQL Server Data-tier Application” instead of a “SQL Server 2008 Database” project.

UPDATE: you might also get this error message if you import the schema from a SQL 2005 database into a SQL Server 2008 Database Project.

A world with free mobile calls

I haven’t posted here for a while because I just went on a month long holiday in India, which was quite an experience.

One interesting thing about India was that cellphones are everywhere and the calls are cheap. Like, real cheap. The standard rate is about 1 rupee (USD $0.02) per minute. But if you’re calling someone on the same mobile network as you, then calls are half a rupee (USD $0.01) per minute. This is cheap even for Indians – you can’t buy anything for 1 rupee in India. So it’s practically free to make a quick one minute call.

Interestingly, to send a text message is about the same price – 1 rupee. Suffice to say no one ever texts in India, since it’s quicker and easier to make a call.

So, what do you expect would happen in a world with practically free communication? Spam of course. People who’d been in India a while ignored their text messages, because the only people who ever send texts are companies spamming. Likewise, it was all too common to get calls from unknown numbers which would be a recorded advertisement.

Apparently you can request your number be added to a “do not call” list and it actually works.

Nokia N900

Only a couple of hours after posting my 2nd ever blog post here, my rant about the iPhone, some guy from WOMWorld Nokia in the UK contacted me, and asked if I’d like to try out their Nokia N900 for 2 weeks. Umm, OK!

I’ll be honest: I hadn’t heard of this phone before. I keep an eye on the tech blogs but I really only pay attention to iPhone, Android and Windows Mobile 7 news. But a quick glance at the Nokia UK website told me that the N900 is their top of the line smartphone.
N900

The Good

Like every other phone on the planet (bar one), it has an SMS length counter. Yay! It feels like a phone, with its removable battery. It has a physical QWERTY keyboard. You can run multiple apps at the same time. Its web browser is great, with accurate rendering of the sites I looked at and Flash support. Removable upgradeable microSD cards.
My brother had a play with it – he doesn’t have a smartphone, and he thought it was much easier to use than my iPhone.

The Bad

I didn’t like the build quality, it felt too plasticy. I didn’t like the resistive touchscreen, to me it felt like I had to thump it harder than I’d like to. And it wasn’t precise either, sometimes I’d click the wrong link while browsing web pages. It comes with a stylus but I kept forgetting to use that, and who wants to use a stylus anyway. The OS occasionally felt slow and not as snappy as the iPhone 3GS. It was stuck in landscape mode – no portrait mode here folks, except when making a call.
I couldn’t figure out the software suite for Windows at all. I wasn’t sure which package to download from the Nokia website, so I tried "Nokia PC suite", and that was crap. I couldn’t figure out how to load music onto the phone at all so I couldn’t try out its media abilities. The maps application was nowhere near as slick as Maps on the iPhone.

vs. the iPhone

A note about the keyboard: I always moan about the iPhone’s on screen keyboard so I thought I would love the N900s physical keyboard, but it seems like I must have gotten used to the iPhone’s keyboard because I can type twice as fast on the iPhone than on the N900.

So on paper, the N900 wins hands down – removable battery, physical keyboard, upgradeable storage, multitasking and Flash support. The iPhone is slimmer though which makes it better looking.
If you don’t already have an iPhone then the N900 is a real contender.

However, the iPhone, for all it’s weaknesses, offers a slicker overall experience, and looks way cooler even if every man and his dog has one these days. A phone has to look bling after all. And the iPhone wins because it is so much more snappy and responsive. I was happy to send the N900 back and go back to my iPhone.

AND Search using SQL Server Full Text Search

DISCLAIMER: I’m new to SQL Server Full Text Search, so if what I’m saying here is wrong then let me know.

Sticking with the Knowledge Base project, we had to implement search. We decided to use SQL Server Full Text Search for this. Once the fulltext catalogs were setup, the query my colleague wrote looked like this:

SELECT [Key], [Rank]
FROM FREETEXTTABLE(dbo.Question, QuestionText, 'building consent form', 100)

The problem with this search is that it will return results which contain any one of the keywords. So if a user enters “building consent form”, a page only needs to contain building OR consent OR form and it will be included in the results. I call this an OR search. This reminds me of the early days of search engines, (AltaVista.com et al), which used to work the same way.

AltaVista c.1999. Remember?

Back in the AltaVista days, if you wanted only pages with every search term you had to use AND between each search term e.g. [building AND consent AND form]. Nowadays you don’t need to type the ANDs; if you enter [building consent form] into Google, it will only return results which contain building, consent, and form. This is what I call an AND search.

SQL Server Full Text Search seems to be stuck in the late 1990s days of OR searches. As demonstrated above it will return a hit for any one of the search words. And there doesn’t seem to be an easy way to tell it to do an AND search.

AND Search

To do an AND search with SQL Server Full Text Search, you need to query the CONTAINSTABLE.

SELECT	[Key], [Rank], 1
FROM	CONTAINSTABLE(dbo.Question, QuestionText, '"building" AND "consent" AND "form"', 100)

Because we’re querying the CONTAINSTABLE and not the FREETEXTTABLE we lose all Full Text Search’s nice fuzziness around the search terms, i.e. in the above example, Questions which contain “buildings”, “consent”, and “form” won’t be returned. To help we can do the following:

SELECT	[Key], [Rank], 1
FROM	CONTAINSTABLE(dbo.Question, QuestionText, '"building*" AND "consent*" AND "form*"', 100)

Stop right there

Another problem with this is stopwords, aka noise words (SQL Server 2005 calls them noise words, 2008 stopwords). SQL Server has a list of around 150 common words, which are ignored by the Full Text Search engine. e.g. about, after, all, also, an, and, another etc. You can view the English stopwords in SQL Server 2008 like so:

select * from sys.fulltext_system_stopwords where language_id = 1033

In my table there is a Question titled “What are building consents?”. If we search for “what are building consents” like so:

SELECT	[Key], [Rank], 1
FROM	CONTAINSTABLE(dbo.Question, QuestionText, '"what*" AND "are*" AND "building*" AND "consents*"', 100)

because “are” is a stopword (and so is “what”) we won’t get any results!
There is a workaround – to wipe the list of stop words! The problem with doing this is that the list of stop words is setup server-wide on the SQL Server itself. In our production environment other applications are using the same SQL Server instance (but of course we have our own KnowledgeBase database), so this wasn’t an option.

Then the customer added a new requirement – if a Question contains the exact search phrase then that should be returned first. Adding the code for that use case fixes my search.

CREATE PROCEDURE dbo.Search
(
@keywords NVARCHAR(4000), — e.g. ‘”building*” AND “consent*” AND “form*”‘
@searchPhrase NVARCHAR(4000) — e.g. ‘building consent form’
)
AS
BEGIN
— Tmp table —
DECLARE @searchResults TABLE
(
QuestionID int,
Rank int,
ExactMatchToQuestionText bit default 0
)

— Question contains keywords in order —
INSERT @searchResults (QuestionID, ExactMatchToQuestionText)
SELECT QuestionID, 1
FROM Question
WHERE QuestionText LIKE ‘%’ + @searchPhrase + ‘%’

— Free Text Search matches —
INSERT @searchResults (QuestionID, Rank, ExactMatchToQuestionText)
SELECT [Key], [Rank], 0
FROM CONTAINSTABLE(dbo.Question, QuestionText, @keywords, 100)
AND [Key] not in (SELECT QuestionID from @searchResults)

— Return —
SELECT Question.*
FROM @searchResults [Results]
JOIN Question ON Results.QuestionID = Question.QuestionID
ORDER BY
Results.ExactMatchToQuestionText DESC,
Results.Rank DESC
END

Still wrong

This code still has a bug – a search for [what are building consents] returns 1 hit, but a search for [what building consents] returns 0, because “what” is a SQL Server stopword. D’oh!
At this point I am better off throwing away SQL Full Text Search and completely rolling my own query.
So why doesn’t SQL Server Full Text Search include a simple option for doing AND searches against the FREETEXTSEARCH table?
And, does anyone know a better way to do AND searches using SQL Server Full Text Search?

Enabling browser back button on cascading dropdowns with jQuery BBQ plugin

What’s this? I know you thought this was supposed to be a .NET blog, yet so far all I’ve posted is jQuery and iPhone stuff. Well the idea of this blog is that I will post stuff I’m learning on the job, and at the moment I’m learning a lot about jQuery, so here we go.

This continues on from the Knowledge Base project I used in a previous post. In this example, I start out on the “Help” page. I navigate to the Browse page, and then within the Browse page I we have a page which shows/hides different sections when the mouse is clicked.

In this example, I start on the Help page and navigate to a FAQ using cascading drop-downs. I choose a value from my “level1” dropdown, Animal Control. The “level2” drop down is populated with Animal Control-related topics, and I choose Dogs. The results pane is loaded with Dog-related FAQs, and I expand the first result, about micro-chipping dogs. I click a link to view the Dogs topic, and then click the browser back button. The browser takes me back to the Help page, but my previous state (viewing the “Do the dogs need to be microchipped” FAQ) isn’t displayed, instead the Help page is in its initial state.

We want to make it so that when the back button is clicked, we’ll be returned to the FAQ we were viewing on the Help page.

Existing Code

$(document).ready(function() {
  $('.dropdown').change(onSelectChange);
  loadServices(); // loads all the Services into the level1 dropdown
}

function onSelectChange() {     
   var selected = $("#" + this.id + " option:selected").val(); // the new selected value     
   if (this.id === 'level1') { // 'this' is the dropdown that was changed       
      loadTopicsForService(selected); // loads topics into the level2 dropdown with AJAX     
   } else if (this.id === 'level2') {
      showAnswers(selected); // shows FAQs for the selected topic with AJAX
   }
}

Plugins

To get the back button working, we need to kind of trick the browser into thinking that we’re navigating up and down within the same page by adding # tags to the address. There are a number of jQuery plugins for doing this and I tried out a few – jquery history plugin, history, and jQuery BBQ.

I originally got it working with jQuery history, but it wouldn’t work properly for IE7. So I tried again with jQuery BBQ and it worked fine across all our target browsers – IE6, IE7, IE8 and FireFox.

Code changes

First, I changed the dropdown change event. Instead of populating the drop downs by calling loadTopicsForService() or showing FAQs by calling showAnswers, I’ll call bbq.pushState:

$(document).ready(function() {     
  $(window).bind("hashchange", historyCallback); // needed for bbq plugin to work     
  
  $('.dropdown').change(function() {
    var dropdown = $(this);
    var id = this.id;
    var selected = dropdown.find("option:selected").val();
    if (selected != 0) {
      if (dropdown === "level1") {
        $.bbq.pushState({ level1: selected }, 2); // merge_mode=2 means wipe out other params (e.g. level2)
      } 
      else if (dropdown === "level2") {
        $.bbq.pushState({ level2: selected });
    }
  });

  loadServices(); // loads all the Services into the level1 dropdown
  historyCallback(); // manually call the bqq history callback, to process the # tags if any were previously 
                          // added. This is called when we load the page by pressing back, fwd, or refresh.
}

Now when I select “Animal Control” from the level 1 (aka Service) drop-down, jQuery.BBQ will 1. add “#level1=1” to the current page location, i.e. the address bar in the browser, and 2. call my historyCallback function which will do the work that used to be defined in the dropdown change event. #1 tricks the web browser into thinking that I’m now on a different page, which adds a new entry into the browser’s history so that clicking Back in the browser will take me to the previous page.

Now we need to define our callbacks which actually do the work of populating the cascading drop downs and displaying FAQs. Remember, historyCallback() will now do the work that used to be defined in onSelectChange().

function historyCallback(e) {
  levelOneCallback();     
  levelTwoCallback();
}

function levelOneCallback() {
  var L1 = $.bbq.getState("level1");     
  if (L1 != undefined && L1 != 0) {
    $('#level1').val(L1); // set dropdown value - needed for when fwd is clicked
    loadTopicsForService(L1); // loads topics into the level2 dropdown with AJAX
  }
  else { // level1 not specified so reset the page         
    $('#level1').val(0);
    $('#row2').hide();
  }
}

levelOneCallback() asks jQuery.BBQ if the level1 has been selected, and if it has been it loads up the level2 drop down. If level1 isn’t specified then we should explicitly reset the level1 dropdown.

The rest of the code is here:

function levelTwoCallback() {
  var L2 = $.bbq.getState("level2");
  if (L2 != undefined && L2 != 0) {
    $('#level2').val(L2); // set dropdown - needed when fwd is clicked
    showAnswers($("#level1").val(), selected);
  } 
  else { // level2 not in querystring, so reset level2
    $('#level2').val(0);
  }
}

So, when I choose a value from the first dropdown, #level1=3 is added to the URL of the page. When I choose a value from the second dropdown, #level2=7 is added. For brevity I have omitted the code which adds #question=55 to the URL when a question is clicked.

Here’s a video of the final result:

Once again I navigate through the drop downs, as before, but this time when I click back, we’re brought back to the previous state which is viewing the question about microchipping dogs. As I keep clicking back, each previous state is loaded. And then when I click Fwd, my previous actions with the dropdowns are repeated!

I’d like to thank “Cowboy” Ben Alman for the jQuery BBQ library and also for suggesting some improvements to my code.

Hope that helps someone.

Visual Studio 2008 SP1 locks up after installing Office 2010 RC

Since yesterday I’ve been having weird issue with Visual Studio 2008 SP1 locking up whenever I edit HTML files. Basically, the interface stops responding and any click anywhere within Visual Studio (e.g. Solution Explorer, within the HTML file itself, the menus) causes a system beep – ding!

It’s really annoying obviously and the only workaround is to kill Visual Studio in Task Manager.

Then when you restart Visual Studio it’ll load up the HTML file you were working on, and if you’re not careful it locks up again.

This started happening yesterday, when I uninstalled the Beta of Office 2010 and installed the RC.

Update: I found the solution, thanks to this blog post.

When Visual Studio hangs it spawns a setup process that never exits. The setup.exe is located at:
C:\Program Files (x86)\Common Files\microsoft shared\OFFICE12\Office Setup Controller\Setup.exe

All I did was run that setup.exe, and chose Repair. It repaired “Microsoft Visual Studio Web Authoring Component”, and once it was done Visual Studio stopped locking up.

Why the iPhone 3GS ain’t all that

People who know me tell me “Matt, you’re the only iPhone owner I know who hates the iPhone”.  Here’s why.

It’s a crap phone

As a phone, it’s a piece of crap. My main moan is about it’s uselessness in text messaging. You see dear reader, here in NZ calls are ridiculously expensive so text messaging is very important. I hardly ever call anyone since the plan I’m on for $40 per month only gives me 20 minutes of calls and 100 text messages per month. Now since I also only get 100 txts, I’m reluctant to write a long txt since that would eat into my 100 per month quota – but guess what – the iPhone 3GS doesn’t tell you how many characters your text message is! This basic functionality, which EVERY cellphone has, the iPhone does not.

There is a workaround – I had to download a separate 3rd party app called “SMS Counter”.

Update (May 2011): An SMS length counter was included in the iOS 4.0 update, released June 2010. It only took me 11 months to find it! (Settings -> Messages -> Character count)

The other problem that no one mentions is that the touch screen sucks for typing on. I can txt faster and easier on my Nokia with predictive texting. And the Nokia is much easier to txt while walking – “I’m on my way, see you in 10”. With the iPhone’s touch screen keyboard I always have to stop walking to txt.

It’s a crap mp3 player

It bugs me how Apple market the iPod Touch as “the best music player ever” when it doesn’t even have functionality that my 5 year old iPod does, and which I use every day – Shuffle Albums. All I do is press play on my iPod and it’ll pick a random album, play every track on that album in order, then when its finished it’ll pick another album at random and play every track on that album in order, etc. This is the only way I ever listen to my music. One button – boom, a random album, played in order. Guess what, the much newer, cooler iPod Touch (and the iPhone 3GS) doesn’t have this. Dammit, don’t make me think about what album I wanna listen to! Suck.

The rest is pretty good

OK, so the rest of the iPhone 3GS is pretty good. It’s nice and fast, and the web browser is OK (although it doesn’t have Flash support, which is a big deal).

So it’s still probably the best smartphone available, which is why I have it – I really just wanted to have wikipedia in my pocket. But as a phone, which is it’s core function after all, it sucks.