mo.notono.us

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: , , ,