mo.notono.us

Tuesday, January 26, 2010

Testing sms to blog functionality. Not sure why i'd do this instead of blogging by email, but (geeky) cool nonetheless...

Tuesday, January 19, 2010

Today’s Prediction: Republicans beat Democrats 41-59

“See, it’s not that the democrats are playing checkers and the republicans are playing chess.  It’s that the republicans are playing chess, and the democrats are in the nurse’s office because once again they’ve glued their balls to their thighs.”
- Jon Stewart @09:10/10:05

Labels: , ,

Tuesday, January 12, 2010

Mission Failure

Just read a notice from BlackBerry that there is a software update for my phone.  Not only did they completely gfail to sell me on the upgrade (what’s new?), they inject such a barrage of technical-ese that anyone’s eyes would glaze over.

Something tells me Apple would not have sent the same kind of email – see highlighted section below…

BlackBerry Software Update Notification
Update Today!
----------------------------------------------------
What else can your BlackBerry® smartphone do for you?

Find out when you update to the latest smartphone software! This free update is ready and waiting to help you do more with your BlackBerry smartphone. To update today visit http://www.blackberry.com/updates

New ways to work and play!

   * As an aid to comprehension, this section provides a brief overview of the life-cycle of a device upgrade.  * Each OTASL capable device will contain one or more OTASL Service Records(SR) each identifying a Device Manager (DM). The DM may be located at RIM, may be part of a BES or, in future, could be associated with a 3rd party application provider. Each SR will identify the applications which are of interest to the corresponding DM. SRs may be delivered by PRV, BES or in an upgrade application loaded OTA. … (it goes on, but there’s no point).

Labels: , , , ,

Monday, January 11, 2010

Well, I guess it’s settled then.

Me: "Who did you play with today, Erik?"

- "Divon... and Riley, when I grow up, you know daddy, when I grow up I'm gonna marry Riley!"

"Really?"

- "Yeah! Cause we're the same color!"

"Huh?"

- "Yeah, we're both light!  And you're light and mommy's light and I'm light and Lottie's light, so.."

"You know you don't have to marry someone who has the same skin color as you, Erik - you can marry anybody you like!"

- "You can?  ... I'm gonna marry Riley.  And when I grow up, I'm gonna be a dogtornarian!"

"Really - a dogtornarian?"

- "Yeah, a dogtornarian is a doctor for dogs.  And he gives the dogs shots, and the dogs are really good, and they get a flu shot so they don't get sick!  And when Riley grows up, she's gonna be a vegternarian, but she's only going to treat cats, and we're gonna treat both dogs AND cats!  And we're gonna have a little baby, and  the baby is going to to a school, and we're going to go to work!  Just like Lottie and me go to school, and you and mommy go to work!"

...

- "Daddy, we're having a great conbersation, do you like it?"

"Yes, Erik, I do.  A lot."

Labels: , ,

Wednesday, January 06, 2010

MVC Route Constraint to Exclude Values

For Album Credits I wanted to allow personalized urls of the format http://albumcredits.com/yournamehere.  This turned out to be quite an interesting routing exercise.

Since this is an MVC app, our standard url format is of the usual http://albumcredits.com/{controller}/{action}/{index} kind, and for some pages, I need to allow the url to simply specify the controller, defaulting the action to index – again, the usual ASP.NET MVC pattern.

I was familiar with the constraint parameter option for the AddRoute method, but had never studied it in much detail – we’d used it to limit certain indexes to be numeric, but that was all.  For the root-level personalized urls we needed a more robust constraint – specifically we needed to exclude any controller from the list of valid personalized Urls.

I first spent more time than I cared to trying to come up with a regular expression pattern that would NOT match the list of controller names – it looked something like this:
^(?:(?!\b(foo|bar)\b).)*$
(thanks to Justin Poliey/stackoverflow.com) where foo and bar, etc were the controller names to NOT match.

Not until after I got that to work did I think to google “mvc custom route constraint”.  Of course the MS MVC team was smarter than that – custom route constraints are really very straight forward…

For my purposes, I went with David Hayden’s approach – the code below is essentially the same as his, just with the logic reversed.

using System;
using System.Linq;
using System.Web;
using System.Web.Routing;

namespace AlbumCredits.Web
{
	/// <summary>
	/// Route constraint that returns true if the parameter value is not one of the excluded values.
	/// </summary>
	/// <example>A controller constraint like 
	/// <code>new { controller = new ExcludeValuesConstraint("foo", "bar") }</code>
	/// will match "blah" or "snort" but will not match "foo" or "bar".
	/// </example>
	public class ExcludeValuesConstraint : IRouteConstraint
	{
		private readonly string[] _excludeValues;
		/// <summary>
		/// Initializes a new instance of the <see cref="ExcludeValuesConstraint"/> class.
		/// Example: <code>new { controller = new ExcludeValuesConstraint("foo", "bar") }</code>
		/// will match "blah" or "snort" but will not match "foo" or "bar".
		/// </summary>
		/// <param name="excludeValues">The excluded values.</param>
		public ExcludeValuesConstraint(params string[] excludeValues)
		{
			_excludeValues = excludeValues;
		}

		/// <summary>
		/// Determines whether the URL parameter contains a valid value for this constraint.
		/// </summary>
		/// <param name="httpContext">An object that encapsulates information about the HTTP request.</param>
		/// <param name="route">The object that this constraint belongs to.</param>
		/// <param name="parameterName">The name of the parameter that is being checked.</param>
		/// <param name="values">An object that contains the parameters for the URL.</param>
		/// <param name="routeDirection">An object that indicates whether the constraint check is being performed when an incoming request is being handled or when a URL is being generated.</param>
		/// <returns>
		/// true if the URL parameter contains a valid value; otherwise, false.
		/// </returns>
		public bool Match(HttpContextBase httpContext, Route route, string parameterName, 
			RouteValueDictionary values, RouteDirection routeDirection)
		{
			return !(_excludeValues.Contains(values[parameterName].ToString(), StringComparer.InvariantCultureIgnoreCase));
		}
	}
}

I can now use this when setting up my Route Table like this:

	routes.MapRoute("PersonalizedUrl",
		/* for urls like  */ "{personalizedUrl}",
		/* route defaults */ new { controller = MVC.Profile.Name, action = MVC.Profile.Actions.IndexByPersonalizedUrl, personalizedUrl = string.Empty },
		/* where          */ new { personalizedUrl = new ExcludeValuesConstraint(ControllerNameArray) }
	);

Labels: , , ,

Friday, December 04, 2009

Getting the ContentType from an ItemAdding Event Handler

Inside an event handler for an ItemAdded event, getting the List Item’s ContentType is as easy as

var contentType = properties.ListItem.ContentType;

However, for ItemAdding, properties.ListItem is null.

Luckily, the ContentType name and Id are part of the properties.AfterProperties collection – the following will work:

SPList list = properties.List; 
string contentTypeName = properties.AfterProperties["ContentType"].ToString(); 
SPContentType contentType = list.ContentTypes[contentTypeName]; 
SPFieldCollection fields = contentType.Fields;

Between the AfterProperties field values and the content type field collection, you typically have all that you need... Just remember to not depend on properties.ListItem.

Labels: , , , , ,

Wednesday, December 02, 2009

SharePoint 2010: Managed Metadata fields and the TaxonomyHiddenList

Notes to self and others:

When you create a Managed Metadata (aka Taxonomy) field in a SharePoint 2010 list or library, the field’s schema will look something like this:

<field id="{f6d2b908-4ed4-42f8-a491-e1177ed57596}" version="1"
rowordinal="0" colname="int3" name="Tags" staticname="Tags"
sourceid="{58701f50-42a0-4c3d-8fdd-fb6f3a798bc4}"
enforceuniquevalues="FALSE" required="FALSE"
showfield="Term1033"
webid="956683ae-54b0-46c4-8fe6-8e14309b6fcd"
list="{4c231f71-5ed3-49aa-a62f-3fbb9a900bd0}"
displayname="Tags" type="TaxonomyFieldType">

<default>1;#A|6832dce7-bd84-4afc-8311-d5a1367dc282</default> <customization> <arrayofproperty> <property> <name>SspId</name> <value xmlns:p4="x-instance" p4:type="q1:string"
xmlns:q1="x">5dc61acc-dfa4-41dd-aa85-dd71d054ab1f</value> </property> <property> <name>GroupId</name> </property> ...

(field definition and namespaces shortened for readability)

Some of the notable attributes are:

  • Type – obvious
  • WebId - The current list’s web
  • SourceID - The current list’s id
  • List - The ID of a “TaxonomyHiddenList” which resides in the current web, but as the name implies, is hidden.
  • ShowField - the field in the TaxonomyHiddenList to display – looks like it’s locale enabled.
  • The SspId property is also crucial – it is the TermStore ID used to access the TermSet – more on that in another blog post…

The TaxonomyHiddenList’s schema looks like most lists’ schema – HUGE.  Some of the interesting fields are:

  • IdForTerm - in the case of the default value above (A) this field’s value matches the GUID 6832dce7-bd84-4afc-8311-d5a1367dc282
  • IdForTermSet - this appears to be the term set id from the MM data store – I have another column in my list which is not tied to a centrally managed metadata store – its value shows as 0000….-00… etc.
  • IdForTermStore – in my instance, this GUID is the same for both the managed and non-managed metadata field – matches the SspId above.

Labels: , ,

Tuesday, November 17, 2009

How to Upgrade a WSPBuilder Project to SharePoint 2010

<UPDATE date=”2009.12.02”>: WSPBuilder Extensions 2010 Beta 1.3 (or newer) makes the below mostly obsolete – though I was still not able to deploy using the VS add-in – I got a message stating my SPTimer Service was not running.</update>

Based on advice from Bjørn Furuknap (@furuknap, http://furuknap.blogspot.com/2009/11/using-wspbuilder-for-sharepoint-2010.html) I was able to deploy to SP 2010 a rather complex WSPBuilder-based SharePoint solution that I first recompiled in VS 2010.

Steps were:

  1. Download the command line version, with the x64 cablib dll
  2. Upgrade your VS 2008/2005 solution to VS 2010
  3. Replace your 12 references with their 14 equivalents - most will be found in C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\ISAPI
  4. Compile your VS solution - this SHOULD work
  5. Use the wspbuilder command line tool to create, but not deploy your wsp (use wspbuilder -help for instructions)
  6. Use stsadm -o addsolution -filename <yourwsp> to deploy the solution

Labels: , , , , ,

Monday, November 09, 2009

SharePoint Office Server 2010, on Windows 7, in Google Chrome

image

It took a few hours of downloading, a few hours of installing (with workarounds), but I now have SharePoint 2010 Office Server Beta (aka the MOSS upgrade) running on my Windows 7 laptop; and as can be seen, it renders beautifully in Google Chrome. <UPDATE1> installed and uninstalled the dang thing several times, with the same result:  I keep getting WCF errors:

System.Configuration.ConfigurationErrorsException: Unrecognized attribute 'allowInsecureTransport'. Note that attribute names are case-sensitive. (C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\WebClients\Profile\client.config line 56)

</UPDATE1>

<UPDATE2>
I completely uninstalled every bit of SharePoint 2010, Visual Studio 2010, .net 4, and every prerequisite of SP.  Then reinstalled.  This time I ran the configuration wizard (as admin) while connected to the domain controller (via vpn).  MOST of SP2010 now seems to work.

I then tried adding the local SP admin account (hat I had created for my first install) to the Farm Administrators group.  Same damn WCF error.

So I simply backed up then edited client.config to remove the offensive attribute both from line 57 and from a second instance on line 97.  Then I rebooted and tried the Farm Admin Addition again.

Same error message (note that I am NOT running SPF, I am running the full Office Server version)…

image

… but different cause this time:

Local administrator privilege is required to update the Farm Administrators' group.

Could this be a limitation of running SharePoint2010 on Windows 7?
</UPDATE2>

The one Another challenge of the installation was that the Configuration wizard would fail repeatedly on step 2 (of 10) when attempting to set up the configuration database.  The error was “user can not be found”.  I then noticed that my domain account did not have full admin rights on my SQL server instance, unless I started SQL Management Studio as Run as Admin.

The workaround I used was to create a local admin account, grant that account full db access, switch to the new admin account, run the configuration wizard, and then switch back to my regular user account.  Crude, but effective.

<UPDATE2>
The better alternative is to connect to your corporate network to gain access to your domain controller.  On my 3rd, 4th and 5th (!) time running the configuration wizard, this was the approach I took, and it seems to work well.  (I understand this is also something that is necessary for the VS 2010 TFS install.)</UPDATE2>

I have no opinions on speed, etc – more of that to come, I’m sure.

<UPDATE3>
So I finally ended up scrapping Windows 7 AND 2008 R2.  With our code, there just was no getting around compatibility issues.  So for now I am running on good ol’ Windows Server 2008 x64.

Can’t wait for MS to release bootable, sysprepped developer vhds for this...
</UPDATE3>

Labels: , , , , , ,

Wednesday, November 04, 2009

Windows 7 Taskbar Icon Progress Indicator

I’ve been ranting too much again, so here’s a rave:  While installing XP Mode in Windows 7 I noticed that the taskbar icon has a built in progress indicator.  That is…

Brilliant!

And that’s only half of it: there’s an API for both progress indicators and icon overlays – see the blog post here.

Labels: , , , ,

This is Business Productivity?

image

Really, Microsoft?  This is the appropriate stock-photo for business productivity?

Are they productive because they are wearing suits?

Are they watching other people work productively?

Is the business being productive without them?

Did they just finish being productive and are now basking in the glow of their success?

(from http://go.microsoft.com/?linkid=9690494)

Labels: , , , , , ,

Tuesday, October 27, 2009

Subtle Google Map/Search Usability Bug

When searching for a company in Google, if Google can determine a map result it’ll show it, along with a crowd-sourcing link to let you verify the accuracy of the business listing:

Capture1

Only problem is that the confirmation message asks the OPPOSITE of the original question:

Capture2

Labels: , ,

Sunday, September 27, 2009

Oops – was I supposed to do that?

Wonder how much of a late-pickup-fee daycare will charge me for being 133 weeks picking up my son?  But thanks Outlook, for the reminder!

image

Labels: , , ,

Saturday, September 19, 2009

Mimicking Outlook 2010’s Conversation View in Outlook 2007

One of Outlook 2010 (and Gmail)’s better features is that conversations are grouped together – that is: if you send me a message, and I reply, and you reply again, all three messages are grouped together – not just the two you sent me.

The latter is what Outlook 2007 considers a “conversation” – I’d call it a monologue, or at best half a conversation.

To get the full duplex conversation thread in Outlook 2007 do the following:

  1. Right-click Search Folders, select new Search Folder
  2. In the New Search Folder dialog, scroll all the way to the bottom, and select Create a custom Search Folder, then click the Choose.. button.
  3. In the Custom Search Folder dialog, enter a name that makes sense to you – I’ll call it Inbox2.0 - SKIP THE CRITERIA, then click Browse… to select which folders to include.
  4. In the Select Folder(s) dialog, select the Inbox and Sent Items folders (and any subfolders/other folders that may make sense for your setup), then click OK
  5. Back in the Custom Search Folder dialog, click OK
  6. Outlook will present a warning that all items in the folders will be included – this is precisely what you want, so click Yes
  7. Your new (Inbox2.0) folder will appear listing all received and sent messages
  8. From the View menu, select Arrange By, Conversation – now your messages are threaded by conversation – the full duplex kind.
  9. From here, you may chose to remove/insert colums (I like to show the 'To' recipient and remove the 'In Folder', and also the 'Subject' column – it is (mostly) a duplicate of the conversation title.

Enjoy your new duplex view.

Labels: , , ,

Friday, August 28, 2009

Making T4MVC comply with CLS

FXCop rule CA1014 tells you to mark your assembly as CLSCompliant. If you adhere to this, your T4MVC (as of build 2.4.01 at least) will throw compiler warnings saying stuff like

Identifier

‘xxxController._Actions’
’xxxController._Views’
’T4MVC._Dummy’

is not CLS-Compliant.

If you have 10 controllers and 50 views this will result in 61 warnings…

The reason is that these are public members that start with an underscore, which is a CLS no-no:

http://stackoverflow.com/questions/1195030/why-is-this-name-not-cls-compliant

 

To solve this, edit the T4MVC.tt file to mark the code with a [CLSCompliant(false)] attribute.  Once you start this, you’ll also find additional warnings from mebers that implement the now-explicitly-non-compliant members, but a few more [CLSCompliant(false)] attribute handles that. Full code in gist below.

Labels: , , , , , ,

Monday, August 24, 2009

Web Setup MSI woes on IIS7 + solution

Just a note to my future self and to anyone else who might stumble on this:

We created an MSI to install our MVC app, but the new test server refused to install it:

The Installer simply stopped, with an Installation interrupted message, and the application event log listed the following:

Windows Installer installed the product. Product Name: XXXXX. Product Version: x.y.z. Product Language: 1033. Installation success or error status: 1603.

The correct google search term here is: Installation success or error status: 1603.

It will lead you to the solution by Ben Noyce at NInitiative:

Long nights and story short, in order to install a web setup project on Windows Server 2008 and IIS 7, you need to install the IIS 6 Metabase Compatibility role service.

Thanks, Ben!

Labels: , , , , , , ,

Making T4MVC comply with StyleCop

On a current MVC project we’re also using the excellent T4MVC template by David Ebbo.  StyleCop however, thinks the generated code is well, less than perfect – it generates some 500 warnings at the moment. 

The solution to this is a simple choice between two options:

Fix the TT file to generate StyleCop compliant code, or exclude the generated T4MVC.cs class from StyleCop.

The pragmatic choice here is of course to exclude the file.  But how?

I first tried to add <ExcludeFromStyleCop>true</ExcludeFromStyleCop> to the Compile entry in the csproj file. Unfortunately that only works with builds from OUTSIDE Visual Studio.

Sergey Shishkin has the answers:

Encapsulating the code in a region that contains the string “generated code” does the trick, but even easier is to simply put a // <auto-generated /> comment at the top of the generated file – which of course means edit the TT file to stick it there.

Would be nice to see this included in the next release….

Labels: , , , , ,

Friday, July 31, 2009

ASP.NET MVC: TagBuilder Extension Methods

I wasn’t happy with the TagBuilder class, so I improved it… See gist below:

This kind of thing could be useful in MOSS WebPart development as well…

Labels: , , , , , , , ,

Wednesday, July 29, 2009

Health care - An open letter to Rep. Elijah E. Cummings (MD-7)

Congressman Elijah Cummings of Maryland's 7th congressional district organized a town-hall style conference call this evening, and ours was one of the phone numbers called.  Below is my response.

To the Honorable Elijah E. Cummings

I'm listening to your town-hall phone call this evening, thank you for organizing this discussion on the healthcare bill. As the President said in his recent press conference - our status quo is a horrible option. But it strikes me that we are facing two main issues here: 1. efficiency, and 2. access.

It is generally agreed upon that in order to afford universal coverage, we will have to lower the cost of coverage. Why is the President and a large portion of the Democratic congressional delegation rushing to increase coverage before proving that we can first improve efficiency?

Your last caller was concerned that increased legislation would only be a source of additional cost rather than savings. Would it not be more prudent - and easier - to first pass a bill that addresses the efficiency question, and then based on the success of that first bill, gradually increases coverage? Would decreasing costs first not also allow people and companies who currently can't afford coverage to finally do so?

You said America is the country that put a man on the moon, but we didn't do it on the first try - we proved the efficiency of the program (and had missteps along the way) before we shot for the moon. I have two kids, and our current national debt is now $11 trillion. With a current budget deficit adding to this debt at a record pace, I find it irresponsible to bet on the efficiency of an unproven program at this time.

Please work with the Blue Dog coalition and the Republicans in Congress and the Senate to emphasize efficiency and cost reduction first and foremost before adding to our bet.

Thank you,

Oskar Austegard

For more information on the need for health care reform, see the non-partisan organization The National Coalition on Health Care.

Labels: , ,

ASP.NET MVC: Corrected Moq based MvcMockHelpers

I've been reading the mostly excellent Pro ASP.NET MVC Framework book by Steven Sanderson, but when trying to implement some of his code in my current project (yay – not SharePoint for a change), I encountered a bug by omission.  In Chapter 8, page 248, he shows how to mock the HttpContext using Moq.  Works great for most urls, but bombs if you include a Query String parameter.
 
Steve Michelotti pointed me to what he uses: the Hanselman/Kzu based MvcMockHelpers class.  Unfortunately it too has some issues (at least as written in Scott’s old post), so below is an updated version.  Get it from github.
 
Use and abuse at will

Labels: , , , , , ,

Tuesday, July 14, 2009

Food: SEI Restaurant

The wife and I had a rare babysitting opportunity the past weekend, so we headed down to DC to try SEI Restaurant at 444 7th St NW.  It’s a Japanese/Asian fusion-style place, with an interesting menu.  Since I found the receipt, I figured I’d give our meal a quick review:

Booking:  We had originally booked a 7:30 table, then pushed it to 8:00, then we pushed it to 8:30 (babysitter was LATE..).  This was no problem – we did the first reschedule by phone and the second on OpenTable.  Yay OpenTable…

Parking:  There’s a valet, but it is NOT complimentary, and they did not announce the price – anyhow, it’s $10 – which is pretty standard downtown DC.  Would have been nice to know up front though.

Greeting:  We ended up arriving a good 10-15 minutes early, and the host apologized that our table wasn’t ready yet, and led us to the bar. 

Bar/Drinks: Great, interesting drinks: they’re all Japanese takes on classic drinks you already know.  Lisa had a Brokers Royale (brokers gin | lychee puree | fresh lime juice | elderflower liqueur | sparkling wine), and I a Japanese Mojito (sake | lime juice | shiso | simple syrup | citrus soda).  Both were complex, interesting, and quite delicious.  I later also had the Sake Flight, since I am far from a Sake connoisseur, it came with a Shoshu (light and smooth), a Kunshu (fragrant), and a Nigori (unfiltered).  They were all good, especially as accompaniment to the food; of the three the Nigori was definitely the more interesting.

Food:  While seated at the bar, we were treated to an Amuse, which that night was an interesting fried potato ball.  Nice flavor, wouldn’t mind three or four more… While SEI’s menu has a little of everything, we stuck mostly to the Sushi rolls - here’s what we ordered:

  • Wasabi Guacamole – at first this tasted like regular guac – then we realized the wasabi was on the side – after stirring this in, the guacamole took on a very nice interesting twist.  The Wonton chips with scallions also added a nice angle to the dish.
  • Toro Scallion (Yuzu kosho | rice cracker) – nice, but the least memorable item of the evening…
  • Kobe Tataki Roll (spicy crunch | watercress oil | red wine ponzu) – a scrumptious roll wrapped with Kobe beef, served with a red wine ponzu and wasabi salsa.  If (2-3of) this was my entire meal, I’d still be happy.
  • Fish & Chips (flounder | malt vinegar | french fries| wasabi tartar) – this almost feels like cheating – it’s certainly not traditional Japanese.  But it’s so good I didn’t care.  I want one of these now.
  • Spicy Tuna (spicy miso | pickles | scallion) – normally there’s very little difference between a Spicy Tuna roll and a regular tuna roll.  This had a kick to it.  Great as is, no soy/wasabi required.
  • Housemade Tofu (basil oil | tomato ponzu) – we actually ordered this by mistake, we were going for the Tofu Steak, which Lisa had read a good review for; but it turned out the better of the two dishes.  Very smooth and silky, like a savory crème brûlée.  Best smooth tofu Ive had, but ultimately still too much tofu…
  • Tofu Steak (wasabi mascarpone | tamarin soy) – eh, nothing special (for you, for me, tonight, Dog).  Ultimately it was doomed by us ordering it too late, we were both to full.  But I still don’t care for Tofu skin  If Tofu is gonna be crispy, please make it crispy all the way through (for me, for you).
  • Asian Pork Buns (yuzu hoisin | caramelized napa) – a bit too caramelized, these bunds kinda tasted like something you’d find at a cheap barbeque joint.  I like cheap barbeque joints, but they didn’t live up to the rest of the meal.  Plus, by the time I ate them, I was really too full to enjoy them.
  • No desert – we were too full.

Open Table’s rating of SEI is 4 stars, which is deserved. I’ll go again.

Labels: , ,

Monday, July 13, 2009

Surprise, surprise: Microsoft tries to steal Xobni's lunch

Watching the Scobleizer's interview with Microsoft Office Product Manager Chris Bryant showing new functionality in Outlook 2010 it's pretty obvious that Microsoft is not just going to let Xobni have all the fun with social networks and conversations - it will now be baked in...

Remind me why Xobni didn't take Microsoft's money when MS offered it to them?

There’s also lots of additional goodness in Outlook, let’s just hope they’ve cleaned up the ‘extra linebreak’ “function” as well.

Labels: , , ,

Friday, July 10, 2009

5 simple steps for getting rid of Live Messenger Ads

A coworker of mine was just complaining about the annoying ads in Live Messenger – here’s how to fix it

  1. Close Live Messenger and all IE instances
  2. Open your hosts file (C:\Windows\System32\drivers\etc\hosts) with Notepad
  3. Add the following line (this redirects requests for the MSN ad server to your local computer)
    127.0.0.1     rad.msn.com     #Live messenger ads
  4. Save your hosts file
  5. Restart Messenger

That’s it – no more ads

Should you ever want the ads back, or need access to rad.msn.com for some reason, simply remove the line (or comment out the line with a leading #)

Labels: , , ,

Monday, July 06, 2009

Testing gist.github.com as a blog code pastebin

I’m new to github, so therefore I’m also new to gist.  At first I couldn’t figure out what they were, but then I watched this great intro video by ByanL which explains it all quite well.

Github seems a bit too command line focused for my taste, but Gist looks useful by itself.

Below I’m going to embed the source for my Twitter Conversations test page, which I copied into my very first Gist – whoa! it loads immediately in the edit panel in Live Writer, nicely color-coded and everything.  Nice!

Thanks to Rob Conery for moving Subsonic 3 to github, which made me look in the first place.  I’ll be using this.

Labels: , , , ,

Wednesday, July 01, 2009

How about this for a business idea

Seth Godin makes some excellent points in his blog post “Graduate school for unemployed college students”.  Basically he says unemployed college grads should just approach the next 12 months as if it was another year of school, and spend the time contributing to the community while learning marketable skills.  Great concept, but as 3rdgirl and snappers15 point out, this is hard to pull off when faced with student loans or other financial responsibilities.  Seth acknowledges this in his followup-post, “Tough!”, but doesn’t offer these people any actual solutions.

How about this for a business idea - and solution to the grad’s financial problem - :

A joint recruiting and student-loan firm that does four things: 

  1. places college grads with non-profits for part time, minimum-wage paid work ($7,540 per year for a 20hr workweek), plus bare-bones health insurance.
  2. provides study-sessions/instruction/seminars/workshops for real world, marketable skills
  3. provides some form of student-loan deferment for the candidate’s current loans, removing that burden for one year from the grad’s shoulders
  4. acts as a recruiter for the grads, generating recruiting fees (to cover costs)

Come to think of it, why can’t our colleges do this, already?  Or why can’t they provide real-world marketable skills in the first place?

Labels: , , , , ,

Wednesday, June 24, 2009

Our Stage in Life

These just hit the nail on the head (or nails on the heads, in our case) too well not to re-post:

(For Better or For Worse, FBorFW, Yahoo)

Lottie is definitely entering the ‘2s…

And Erik has trouble sleeping…

Sigh (with a smile)…

Labels: ,

Wednesday, June 17, 2009

More complicated JavaScript string tokenizer – and Twitter Conversations

(I'm not sure when I started using the term tokenizer - "formatter" may be more common...)

I’ve experimented in the past with a C# string.Format() alternative that would allow property names as tokens rather than the numeric index based tokens that string.Format() uses.  Hardly a unique approach, and many others did it better.

Here’s another first-draft ‘tokenizer’, this time in JavaScript:

String.prototype.format = function(tokens) {
///<summary>
///This is an extension method for strings, using string or numeric tokens (e.g. {foo} or {0}) to format the string.
///<summary>
///<param name="tokens">One or more replacement values
///if a single object is passed, expects to match tokens with object property names, 
///if a single string, number or boolean, replaces any and all tokens with the string
///if multiple arguments are passed, replaces numeric tokens with the arguments, in the order passed
///</param>
///<returns>the string with matched tokens replaced</returns>
  var text = this;
  try  {
    switch (arguments.length) {
      case 0: {
        return this;
      };
      case 1: 
      {
        switch (typeof tokens) {
          case "object":
          {
            //loop through all the properties in the object and replace tokens matching the names
            var token;
            for (token in tokens) {
              if (!tokens.hasOwnProperty(token) || typeof tokens[token] === 'function') {
                break;
              }
              //else
              text = text.replace(new RegExp("\\{" + token + "\\}", "gi"), tokens[token])
            }
            return text;
          };
        case "string":
        case "number":
        case "boolean":
          {
            return text.replace(/{[a-z0-9]*}/gi, tokens.toString());
          };
        default:
            return text;
        };
      };
      default:
      {
        //if multiple parameters, assume numeric tokens, where each number matches the argument position
        for (var i = 0; i < arguments.length; i++) {
          text = text.replace(new RegExp("\\{" + i + "\\}", "gi"), arguments[i].toString());
        }
        return text;
      };
    };
  } catch (e) {
    return text;
  }
};

The comment (in VS Intellisense format) is pretty self-explanatory, note that it doesn’t allow any special formatting as String.Format does, nor does it even support escaping {}‘s – in general it is quite crude.

That said, when used within it’s limitations it works - below are a couple of actual usage scenarios, both from my ongoing Twitter Conversations experiment:

var url = "http://search.twitter.com/search.json?q=from:{p1}+to:{p2}+OR+from:{p2}+to:{p1}&since={d1}&until={d2}&rpp=50".format({ 
  p1: $("#p1").attr("value"), 
  p2: $("#p2").attr("value"),
  d1: $("#d1alt").attr("value"), 
  d2: $("#d2alt").attr("value")
});

and

$.getJSON(url + "&callback=?", function(data) {
  $.each(data.results, function(i, result) {
    content = ' \
<p> \
	<a href="http://twitter.com/{from_user}"><img src="{profile_image_url}" />{from_user}</a> \
	(<a href="http://twitter.com/{from_user}/statuses/{id}">{created_at}</a>): \
	{text} \
</p>'.format(result);

The working Twitter Conversations sample is here, as you can tell it’s really just a wrapper around the Twitter Search API.

Labels: , , ,

Tuesday, June 16, 2009

Twitter Conversations: More fun with Yahoo Pipes

<Preface>
Lets imagine that you can’t actually do all of this directly in Twitter Search like this:
http://search.twitter.com/search?q=from:jaketapper+to:senjohnmccain+OR+from:senjohnmccain+to:jaketapper&since=2009-06-16&until=2009-06-16&rpp=50
</Preface>

(Now contrived) How To follows:

I stumbled upon an impromptu (or so they claim) twitter interview between Jake Tapper (@jaketapper) of ABC and Sen. John McCain (@senjohnmccain).  Unfortunately, short of following both of them, it was hard to get a gist of the actual conversation – I tried the Conversation view on Twitter Search, and a couple of other services, like Tweader (appears broken) and Quotably (dead).

Twitter Search has an advanced search/allows parameters to specify searching for tweets from one person to another, but that only gives you half a conversation.  And as far as I can tell, Twitter search doesn’t allow multiple from:/to: pairs.  Enter Yahoo Pipes’ union module:

This module merges up to five separate sources of items into a single list of items.

Since Twitter Search results can be had in both Atom and RSS flavor, this means we’re good to go:

<interlude> 
…and this is where I discovered that you could indeed do duplex conversation searches in twitter search, so the rest of this will be short…
</interlude>

See http://pipes.yahoo.com/austegard/twitterconversations; click View Source or Clone to play with it (requires Yahoo id).

Labels: , , , , ,

Simple JavaScript string tokenizer

A crude String.Format equivalent in JavaScript -blatantly copied from frogsbrain

//from http://frogsbrain.wordpress.com/2007/04/28/javascript-stringformat-method/ 
String.format = function(text) { 
    if (arguments.length > 1) { 
        for (var i = 0; i < arguments.length - 1; i++) { 
            text = text.replace(new RegExp("\\{" + i + "\\}", "gi"), arguments[i + 1]); 
        } 
    } 
    return text; 
}; 

#twitcode version (130 characters!)

strf=function(t){a=arguments;if(a.length>1)for(i=0;i<a.length-1;i++)t=t.replace(new RegExp("\\{"+i+"\\}","gi"),a[i+1]);return t}; 

Labels: , ,

Monday, June 15, 2009

3 steps to a better Tech Crunch (feed)

I know I’m not alone in having a lack of confidence in the journalistic ability of Michael Arrington’s employees over at TechCrunch – MG Siegler’s outrage over Microsoft’s latest (rather sad) marketing efforts is just the latest to draw annoyed comments.  With 20-30 posts per day, it gets pretty time-consuming just trying to filter the good stories from the bad, since all that Google Reader gives you is the headline and the start of the body:

image

Wouldn’t it be nice to also know who wrote that headline so that you knew better how to interpret it?  Well, you can do so quite easily, in three simple steps.

Step 1: Create a Yahoo Pipe

Go to http://pipes.yahoo.com/pipes/pipe.edit

image

 

Step 2: Configure your Pipe

Add the following modules:

  • Sources – FetchFeed
  • Operators – Loop
  • String – StringBuilder (drag onto the Loop module)

Configure and connect as below, then save.

image

 

Step 3: Subscribe to your new feed:

image

 

Or, if all that’s too complicated, you can just subscribe to the Pipe I created:

http://pipes.yahoo.com/austegard/tcwa

PS! Here’s a filtered feed for Michael Arrington posts only:

http://pipes.yahoo.com/austegard/tcma

Labels: , , , , ,

Tuesday, June 09, 2009

Outlook's dumbest feature?

image
and the corresponding
image

Why? Why? Why?

It seems 9 out of 10 times the line breaks are significant and SHOULDN’T be removed.

The feature has persisted for about a decade (if not longer – I don’t recall if it was part of 97 and 98); if you don’t know by now, this is how you remove it:

Tools – Options – Preferences – Email Options, Uncheck the Remove… checkbox.

image

Labels: , , , ,

Thursday, June 04, 2009

My contributions to SPExLib

I just discovered Wictor Wilen (et al)’s excellent CodePlex project: SharePoint Extensions Lib, which aims to simplify SharePoint development on .NET 3.5 SP1 and up.

Since I never took the time to figure out how exactly to contribute directly to a CodePlex project, I instead first did an ad-hoc contribution of my (or rather Rob Garrett’s really) SPlist.AddItemOptimized() method.  I then quickly went through the methods that are there so far in SPExLib, and culled from my own library those that overlapped.  Below is the filtered result: these are extension methods not currently in the SPExlib project (as of June 04 2009).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;

namespace SPExLib.SharePoint
{
  #region Site Extensions
  /// <summary>
  /// SPSite Extension Methods
  /// </summary>
  public static class SiteExtensions
  {

    /// <summary>
    /// Gets a built in site field (column).
    /// </summary>
    /// <param name="site">The site.</param>
    /// <param name="builtInFieldId">The built in field id.</param>
    /// <returns>The Built In <see cref="SPField"/>.</returns>
    public static SPField GetBuiltInField(this SPSite site, Guid builtInFieldId)
    {

      return site.RootWeb.Fields[builtInFieldId];
    }

    /// <summary>
    /// Gets a <see cref="List{SPList}"/> of all lists contained within the site collection, including those in the root web and all the sub sites.
    /// </summary>
    /// <param name="site">The site.</param>
    /// <returns>A <see cref="List{SPList}"/> containing all the lists.</returns>
    public static List<SPList> GetAllLists(this SPSite site)
    {
      List<SPList> allLists = new List<SPList>();
      for (int i = 0; i < site.AllWebs.Count; i++)
      {
        using (SPWeb web = site.AllWebs[i])
        {
          allLists.AddRange(web.Lists.Cast<SPList>());
        }
      }
      return allLists;
    }
  }
  #endregion

  #region Web Extensions
  /// <summary>
  /// Extensions to the SPWeb class
  /// </summary>
  public static class WebExtensions
  {
    /// <summary>
    /// Gets the relative URL of the web.
    /// </summary>
    /// <param name="web">The web.</param>
    /// <returns>The Url of the web, relative to the site.</returns>
    public static string GetRelativeUrl(this SPWeb web)
    {
      string siteUrl = web.Site.Url;
      string webUrl = web.Url;
      if (webUrl.Contains(siteUrl))
        return webUrl.Replace(siteUrl, "");
      return null;

    }


    /// <summary>
    /// Gets the list from the list's URL, relative to the Web (which is the url used to create the list).
    /// </summary>
    /// <param name="web">The web.</param>
    /// <param name="listUrl">The list URL, relative to the web (equals the url used to create the list).</param>
    /// <returns>A <see cref="SPList"/> instance, or null, if the list was not found.</returns>
    public static SPList GetListFromListUrl(this SPWeb web, string listUrl)
    {
      SPList result = null;
      listUrl = string.Format("{0}/{1}", web.Url, listUrl);
      try
      {
        result = web.GetList(listUrl);
      }
      catch { }
      return result;
    }
  }
  #endregion

  #region SPList Extensions
  /// <summary>
  /// SPList Extension Methods
  /// </summary>
  public static class ListExtensions
  {
    /// <summary>
    /// Adds a content type to the list, optionally adding the content type fields to the default view for the list.
    /// </summary>
    /// <param name="list">The list.</param>
    /// <param name="contentType">The content type.</param>
    /// <param name="addColumnsToDefaultView">if set to <c>true</c> add columns to the list's default view.</param>
    /// <returns>The <see cref="SPContentType"/> instance.</returns>
    public static SPContentType AddContentType(this SPList list, SPContentType contentType, bool addColumnsToDefaultView)
    {
      if (contentType == null || list.ContentTypes.ContainsContentType(contentType.Id))
        return contentType;

      contentType = list.ContentTypes.Add(contentType);
      if (addColumnsToDefaultView)
      {
        bool viewUpdated = false;
        SPView defaultView = list.DefaultView;
        //SPViewFieldCollection viewFields = list.DefaultView.ViewFields;
        //Add content type fields (but only those not hidden, and also not ContentType and Title)
        foreach (SPField field in contentType.Fields)
        {
          if (!defaultView.ViewFields.Contains(field.InternalName)
            && !field.Hidden && !contentType.FieldLinks[field.Id].Hidden
            && !"|Title|ContentType|Name|".Contains(field.InternalName)
            )
          {
            defaultView.ViewFields.Add(field);
            viewUpdated = true;
          }
        }
        if (viewUpdated)
        {
          defaultView.DefaultView = true;
          defaultView.Update();
        }
      }
      list.Update();
      return contentType;
    }
  #endregion
    #region SPDocumentLibrary

    /// <summary>
    /// Adds the content type to the document library, optionally adding the content type fields to the default view for the list.
    /// </summary>
    /// <param name="documentLibrary">The document library.</param>
    /// <param name="contentType">The content type.</param>
    /// <param name="addColumnsToDefaultView">if set to <c>true</c> [add columns to default view].</param>
    /// <returns>The <see cref="SPContentType"/> instance.</returns>
    public static SPContentType AddContentType(this SPDocumentLibrary documentLibrary, SPContentType contentType, bool addColumnsToDefaultView)
    {
      return ((SPList)documentLibrary).AddContentType(contentType, addColumnsToDefaultView);
    }
    #endregion
    #region SPListCollection
    /// <summary>
    /// Creates a list based on the specified title, description, URL, Feature ID, template type, document template type, and options for displaying a link to the list in Quick Launch.
    /// Overloads and corrects the parameter types of the default Add method
    /// </summary>
    /// <param name="listCollection">The list collection.</param>
    /// <param name="title">The title.</param>
    /// <param name="description">The description.</param>
    /// <param name="url">The URL.</param>
    /// <param name="featureId">The feature id.</param>
    /// <param name="templateType">The template type.</param>
    /// <param name="docTemplateType">The doc template type.</param>
    /// <param name="quickLaunchOptions">The quick launch options.</param>
    /// <returns>A GUID that identifies the new list.</returns>
    public static Guid Add(this SPListCollection listCollection, string title, string description, string url, Guid featureId,
      SPListTemplateType templateType, int docTemplateType, SPListTemplate.QuickLaunchOptions quickLaunchOptions)
    {
      return listCollection.Add(title, description, url, featureId.ToString("B").ToUpper(),
        (int)templateType, docTemplateType.ToString(), quickLaunchOptions);
    }
    #endregion
    #region SPViewFieldCollection
    /// <summary>
    /// Determines whether the view field collection contains a field of the specified internal name.
    /// </summary>
    /// <param name="viewFieldCollection">The view field collection.</param>
    /// <param name="internalFieldName">Internal name of the  field.</param>
    /// <returns>
    ///   <c>true</c> if the view field collection contains the field; otherwise, <c>false</c>.
    /// </returns>
    public static bool Contains(this SPViewFieldCollection viewFieldCollection, string internalFieldName)
    {
      return viewFieldCollection.Cast<string>().Any(f => f == internalFieldName);
    }

    /// <summary>
    /// Adds a specified field to the collection.
    /// </summary>
    /// <param name="col">The collection.</param>
    /// <param name="fieldGuid">The field's Guid.</param>
    public static void Add(this SPViewFieldCollection col, Guid fieldGuid)
    {
      col.Add(col.View.ParentList.Fields[fieldGuid]);
    }

    /// <summary>
    /// Deletes the specified field from the collection.
    /// </summary>
    /// <param name="col">The collection.</param>
    /// <param name="fieldGuid">The field's Guid.</param>
    public static void Delete(this SPViewFieldCollection col, Guid fieldGuid)
    {
      col.Delete(col.View.ParentList.Fields[fieldGuid]);
    }

  }
    #endregion

  #region SPContentType (and related classes) extensions
  /// <summary>
  /// SPContentType and related class extensions
  /// </summary>
  public static class ContentTypeExtensions
  {
    #region SPFieldLinkCollection Extensions
    /// <summary>
    /// Adds a field to the <see cref="SPFieldLinkCollection"/>.
    /// </summary>
    /// <param name="fieldLinkCollection">The field link collection.</param>
    /// <param name="field">The field.</param>
    public static void AddField(this SPFieldLinkCollection fieldLinkCollection, SPField field)
    {
      fieldLinkCollection.Add(new SPFieldLink(field));
    }

    /// <summary>
    /// Clears the fields in a <see cref="SPFieldLinkCollection"/>.
    /// </summary>
    /// <param name="fieldLinkCollection">The field link collection.</param>
    public static void ClearFields(this SPFieldLinkCollection fieldLinkCollection)
    {
      for (int i = fieldLinkCollection.Count - 1; i >= 0; i--)
      {
        fieldLinkCollection.Delete(fieldLinkCollection[i].Id);
      }

    }
    #endregion

    #region SPContentType Extensions
    /// <summary>
    /// Adds a field link to the <see cref="SPContentType"/>.
    /// </summary>
    /// <param name="contentType">The content type.</param>
    /// <param name="field">The field.</param>
    public static void AddFieldLink(this SPContentType contentType, SPField field)
    {
      contentType.FieldLinks.Add(new SPFieldLink(field));
    }

    /// <summary>
    /// First clears the field links collection from the content type, then re-adds all Parent content type field links.
    /// </summary>
    /// <param name="contentType">The content type.</param>
    public static void ResetFieldLinks(this SPContentType contentType)
    {
      contentType.FieldLinks.ClearFields();
      foreach (SPFieldLink fl in contentType.Parent.FieldLinks)
      {
        contentType.FieldLinks.Add(fl);
      }
    }


    /// <summary>
    /// Clears the event receivers from the content type.
    /// </summary>
    /// <param name="contentType">The content type.</param>
    public static void ResetEventReceivers(this SPContentType contentType)
    {
      for (int i = contentType.EventReceivers.Count - 1; i >= 0; i--)
      {
        contentType.EventReceivers[i].Delete();
      }
      //TODO: Does a content type inherit it's parent content type receiver?
      //contentType.EventReceivers = contentType.Parent.EventReceivers;
    }

    #endregion

    #region SPContentTypeCollection Extensions
    /// <summary>
    /// Determines whether the <see cref="SPContentTypeCollection"/> contains a content type with the specified name.
    /// </summary>
    /// <param name="contentTypeCollection">The content type collection.</param>
    /// <param name="name">The name.</param>
    /// <returns>
    ///   <c>true</c> if the content type collection contains a content type with the specified name; otherwise, <c>false</c>.
    /// </returns>
    public static bool Contains(this SPContentTypeCollection contentTypeCollection, string name)
    {
      //return contentTypeCollection.Cast<SPContentType>().Any(ct => ct.Name == name);
      return (contentTypeCollection[name] != null);
    }

    /// <summary>
    /// Determines whether the content type collection contains a content type with the specified <see cref="SPContentTypeId"/>.
    /// </summary>
    /// <param name="contentTypeCollection">The content type collection.</param>
    /// <param name="contentTypeID">The content type ID.</param>
    /// <returns>
    ///   <c>true</c> if the content type collection contains a content type with the specified <see cref="SPContentTypeId"/>; otherwise, <c>false</c>.
    /// </returns>
    public static bool ContainsContentType(this SPContentTypeCollection contentTypeCollection, SPContentTypeId contentTypeID)
    {
      //old way
      //for (int i=0; i < contentTypeCollection.Count; i++)
      //{
      //  if (contentTypeCollection[i].Id == contentTypeID)
      //    return true;
      //}
      //return false;

      //new cool way:
      //return contentTypeCollection.Cast<SPContentType>().Any(ct => ct.Id == contentTypeID);

      //simple way
      return (contentTypeCollection[contentTypeID] != null);
    }

    /// <summary>
    /// Deletes a sealed content type by unsealing and un-readonly-ing the content type before attempting to delete it.
    /// </summary>
    /// <param name="contentTypeCollection">The content type collection.</param>
    /// <param name="name">The name.</param>
    /// <returns></returns>
    public static bool DeleteSealedContentType(this SPContentTypeCollection contentTypeCollection, string name)
    {
      bool success = false;
      try
      {
        SPContentType contentType = contentTypeCollection[name];
        contentType.Sealed = false;
        contentType.ReadOnly = false;
        contentTypeCollection.Delete(contentType.Id);
        success = !contentTypeCollection.ContainsContentType(contentType.Id);
      }
      catch { }
      return success;
    }

    #endregion
  }
  #endregion

  /// <summary>
  /// Extension methods for SPFile
  /// </summary>
  public static class SPFileExtensions
  {

    /// <summary>
    /// Gets the file size as string.
    /// </summary>
    /// <param name="file">The file.</param>
    /// <returns></returns>
    public static string GetFileSizeAsString(this SPFile file)
    {
      double s = file.Length;
      string[] format = new string[] { "{0} bytes", "{0} KB", "{0} MB", "{0} GB", "{0} TB", "{0} PB", "{0} EB", "{0} ZB", "{0} YB" };
      int i = 0;
      while (i < format.Length - 1 && s >= 1024)
      {
        s = (int)(100 * s / 1024) / 100.0;
        i++;
      }
      return string.Format(format[i], s.ToString("###,###,###.##"));
    }
  }
}

Labels: , , , ,

Tuesday, June 02, 2009

Amazon Payment Woes Not Quite Over

Uhm, this ain’t right (clik to see full-size pic):

image

Basically, AmazonPayments/JungleDisk decided to charge me 8 (EIGHT!) times for the same service.

Labels: , , , ,

Monday, June 01, 2009

Gmail Voicemail coming soon?

I just started using Vonage, and get my voicemails delivered to my gmail account.  So I tried setting up a filter:

image

It certainly makes sense that Google Voice and Gmail would be integrated one of these days…

Labels: , , ,

Friday, May 22, 2009

Gmail’s number one missing feature

IMAP Client Mode.

I can access Gmail through IMAP, using Gmail as an IMAP server.  But I can’t access, say, my work email (which is an IMAP server) through IMAP, using Gmail as the client.  I can through POP, but that is sub-optimal at best.

Come on Google, I know you can do it.

Labels: , , ,