Wednesday, June 24, 2009
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: experiment, javascript, mashup, me-me-me
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: errors, experiment, mashup, me-me-me, twitter, yahoo
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: c#, experiment, javascript
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:
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
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.
Step 3: Subscribe to your new feed:
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:
Tuesday, June 09, 2009
Outlook's dumbest feature?
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.
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: c#, howto, me-me-me, moss, sharepoint

