<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Island 94 &#187; Drupal</title>
	<atom:link href="http://www.island94.org/tag/Drupal/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.island94.org</link>
	<description>Ben Sheldon&#039;s lost &#38; found</description>
	<lastBuildDate>Wed, 18 Jan 2012 17:04:37 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>A form from my favorites</title>
		<link>http://www.island94.org/2011/03/a-form-from-my-favorites/</link>
		<comments>http://www.island94.org/2011/03/a-form-from-my-favorites/#comments</comments>
		<pubDate>Thu, 17 Mar 2011 16:38:00 +0000</pubDate>
		<dc:creator>Ben</dc:creator>
				<category><![CDATA[Drupal]]></category>
		<category><![CDATA[human interface]]></category>
		<category><![CDATA[pride]]></category>
		<category><![CDATA[webdesign]]></category>

		<guid isPermaLink="false">http://www.island94.org/?p=2549</guid>
		<description><![CDATA[<p><figure title=""><img src="http://www.island94.org/wp-content/uploads/2011/03/brompt-signup-600x501.png" class="attachment-h5bp-post-image wp-post-image" alt="brompt-signup" title="brompt-signup" /></figure></p>Above is the signup form from Brompt, the blog reminder service I built a few years ago for undisciplined bloggers like myself. I'm very proud of this form: it's clear, it contextualizes the data being requested within the functionality of the application, and it drives people to action. There are a few areas for improvement: [...]<p><a href="http://www.island94.org/2011/03/a-form-from-my-favorites/">&#9734; Permalink</a></p>


<strong>Related posts:</strong><ol><li><a href='http://www.island94.org/2011/03/the-48-hour-mobile-web-app-drunken-stumble/' rel='bookmark' title='The 48 hour mobile web app: Drunken Stumble'>The 48 hour mobile web app: Drunken Stumble</a> <small>Last weekend I participated in the Boston Hack Day Challenge, a 48 hour (so I’m not sure why they called...</small></li>
<li><a href='http://www.island94.org/2011/08/7-years-in-boston/' rel='bookmark' title='7 years in Boston'>7 years in Boston</a> <small>This August marks the completion of my 7th year in Boston, with the loose exceptions of the 1 month I...</small></li>
<li><a href='http://www.island94.org/2011/09/meet-americorps-no-more/' rel='bookmark' title='Meet AmeriCorps no more'>Meet AmeriCorps no more</a> <small>This month, the lights on Meet AmeriCorp—a directory and messaging service for AmeriCorps members that launched in 2006—has gone dark...</small></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p><figure title=""><img src="http://www.island94.org/wp-content/uploads/2011/03/brompt-signup-600x501.png" class="attachment-h5bp-post-image wp-post-image" alt="brompt-signup" title="brompt-signup" /></figure></p><p><img class="aligncenter size-medium wp-image-2551" title="brompt-signup" src="http://www.island94.org/wp-content/uploads/2011/03/brompt-signup-600x501.png" alt="" /></p>
<p>Above is the signup form from <a href="http://brompt.com">Brompt</a>, the blog reminder service I built a few years ago for undisciplined bloggers like myself. I'm very proud of this form: it's clear, it contextualizes the data being requested within the functionality of the application, and it drives people to action.</p>
<p>There are a few areas for improvement: asking for your RSS feed rather than your blog's URL is a bit roundabout (autodetection is still in the feature parking lot) and it could use a little explanation at the top in case you bypassed the front page's explanation of the service.</p>
<p>Another reason I'm proud of this form is that it was built using Drupal's Forms API---and Drupal uses awful, unintuitive forms like they were going out of style.</p>


<p><strong>Related posts:</strong><ol><li><a href='http://www.island94.org/2011/03/the-48-hour-mobile-web-app-drunken-stumble/' rel='bookmark' title='The 48 hour mobile web app: Drunken Stumble'>The 48 hour mobile web app: Drunken Stumble</a> <small>Last weekend I participated in the Boston Hack Day Challenge, a 48 hour (so I’m not sure why they called...</small></li>
<li><a href='http://www.island94.org/2011/08/7-years-in-boston/' rel='bookmark' title='7 years in Boston'>7 years in Boston</a> <small>This August marks the completion of my 7th year in Boston, with the loose exceptions of the 1 month I...</small></li>
<li><a href='http://www.island94.org/2011/09/meet-americorps-no-more/' rel='bookmark' title='Meet AmeriCorps no more'>Meet AmeriCorps no more</a> <small>This month, the lights on Meet AmeriCorp—a directory and messaging service for AmeriCorps members that launched in 2006—has gone dark...</small></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.island94.org/2011/03/a-form-from-my-favorites/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>The 48 hour mobile web app: Drunken Stumble</title>
		<link>http://www.island94.org/2011/03/the-48-hour-mobile-web-app-drunken-stumble/</link>
		<comments>http://www.island94.org/2011/03/the-48-hour-mobile-web-app-drunken-stumble/#comments</comments>
		<pubDate>Wed, 02 Mar 2011 22:39:04 +0000</pubDate>
		<dc:creator>Ben</dc:creator>
				<category><![CDATA[Drupal]]></category>
		<category><![CDATA[intoxication]]></category>
		<category><![CDATA[portfolio]]></category>
		<category><![CDATA[project management]]></category>
		<category><![CDATA[webdesign]]></category>

		<guid isPermaLink="false">http://www.island94.org/?p=2501</guid>
		<description><![CDATA[<p><figure title=""><img src="http://www.island94.org/wp-content/uploads/2011/03/Drunken-Stumble-Screens-600x378.png" class="attachment-h5bp-post-image wp-post-image" alt="Drunken Stumble Screens" title="Drunken Stumble Screens" /></figure></p>Last weekend I participated in the Boston Hack Day Challenge, a 48 hour (so I'm not sure why they called it a hack day) competition sponsored by the Boston Globe and held in the MassChallenge workspace. The goal of the event was to develop tools that would improve the lives of Bostonians. My team won [...]<p><a href="http://www.island94.org/2011/03/the-48-hour-mobile-web-app-drunken-stumble/">&#9734; Permalink</a></p>


<strong>Related posts:</strong><ol><li><a href='http://www.island94.org/2011/07/a-modest-web-app-proposal/' rel='bookmark' title='A modest web-app proposal'>A modest web-app proposal</a> <small>Advanced algorithms analyze your social graph to provide deep introspection through animal shapes and SEO-optimized pithy sayings. Once we close Series...</small></li>
<li><a href='http://www.island94.org/2011/08/7-years-in-boston/' rel='bookmark' title='7 years in Boston'>7 years in Boston</a> <small>This August marks the completion of my 7th year in Boston, with the loose exceptions of the 1 month I...</small></li>
<li><a href='http://www.island94.org/2011/03/a-form-from-my-favorites/' rel='bookmark' title='A form from my favorites'>A form from my favorites</a> <small>Above is the signup form from Brompt, the blog reminder service I built a few years ago for undisciplined bloggers...</small></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p><figure title=""><img src="http://www.island94.org/wp-content/uploads/2011/03/Drunken-Stumble-Screens-600x378.png" class="attachment-h5bp-post-image wp-post-image" alt="Drunken Stumble Screens" title="Drunken Stumble Screens" /></figure></p><p><a href="http://drunkenstumble.com"><img class="aligncenter" title="drunkenlogo" src="http://www.island94.org/wp-content/uploads/2011/03/drunkenlogo-500x190.png" alt="" width="500" height="190" /></a></p>
<p><a href="http://www.island94.org/wp-content/uploads/2011/03/Drunken-Stumble-Screens.png"><br />
</a>Last weekend I participated in the <a href="http://beta.boston.com/hackday">Boston Hack Day Challenge</a>, a 48 hour (so I'm not sure why they called it a hack <em>day</em>) competition sponsored by the Boston Globe and held in the MassChallenge workspace. The goal of the event was to develop tools that would improve the lives of Bostonians. My team won "Best Mobile App" as well as the "Crowd Favorite" award for the pub crawl app we built: <a href="http://drunkenstumble.com">Drunken Stumble</a>.</p>
<p><em><strong>Update:</strong> You can also read about the process from some of my teammates: <a href="http://www.nikibrown.com/designoblog/2011/02/28/designing-and-building-a-web-app-in-a-weekend-drunken-stumble/">@nikibrown on the design</a>, <a href="http://www.unruthless.com/blog/post/drunken-stumble">@unruthless on frontend and whip-cracking</a>, and <a href="http://miles-per-hour.com/2011/03/01/drunken-stumble-a-drupal-7-web-app-built-in-a-weekend/">@mikemiles86 on the backend</a> and <a href="http://miles-per-hour.com/2011/03/02/drunken-stumble-a-breakdown/">interface</a>.</em></p>
<p>(And if you're wondering how a pub crawl app would improve the lives of Bostonians, you must not be from Boston.)</p>
<h2>About the Project</h2>
<p>Our team formed Friday night. On the frontend we had <a href="http://twitter.com/nikibrown">@nikibrown</a> (designer) and <a href="http://twitter.com/unruthless">@unruthless</a> (frontend developer)---who knew each other prior to the event. On the backend we had <a href="http://twitter.com/mikemiles86">@mikemiles86</a> (application developer) and <a href="http://twitter.com/bensheldon">myself</a> (API and interface developer)---who happened to be standing near the cheese table.</p>
<p>Together---and with the muse of the open bar---we came up with the idea of creating an application that would help people perform a pub crawl. While other teams  proposed concept demos and APIs, we set our goal to be the delivery of a complete product by the end of the weekend. Defining a minimum viable product meant keeping a lot of great ideas in the parking lot (like drink lists and multi-user crawls) but helped us focus on delivering a tight, attractive and functional pub-crawl application that lets you:</p>
<ul>
<li>use your smartphone to find nearby pubs</li>
<li>get walking directions to that pub</li>
<li>invite your friends to the pub via social media (Twitter and Facebook)</li>
<li>find the next pub for your crawl and get directions to there from your current pub</li>
<li>track your nightly progress (or revisit it the next morning)</li>
<li>call a cab home at the end of the night</li>
</ul>
<h2>About the App</h2>
<p>We built Drunken Stumble as an HTML5 mobile web application---testing it on both iPhone and Android. As an HTML5 mobile web app,  we can access smartphone features (like GPS and Portrait/Landscape modes) as well as quickly write, test and deploy. Both @mikemiles86 and myself are Drupal/PHP developers so (with some reservations) we decided to use Drupal as a framework---using its paths, forms, database and template systems and nothing else.</p>
<p><img class="aligncenter size-full wp-image-2504" title="drunken planning" src="http://www.island94.org/wp-content/uploads/2011/03/drunken-planning.jpeg" alt="Planning documents" /></p>
<p>I focused most of my development on interfacing with external APIs. Originally we planned on using Yelp to provide business data for locating pubs, but their API is extremely limited with only 100 lookups per day. Fortunately, we found an awesome service called <a href="http://simplegeo.com">SimpleGeo</a> that currently offers unlimited lookups for businesses based upon location. Using SimpleGeo we were able to quickly write a rich, location-aware application that works anywhere in the United States, not just in Boston.</p>
<p>Once we knew we had a source for business data, the next step was telling the application where you are. HTML5 offers <a href="http://dev.w3.org/geo/api/spec-source.html">native geolocation</a> which means we can (politely) request exact location data based on your smartphone's GPS. If your smartphone doesn't have GPS (or it's turned off) you can type in an address and we use <a href="http://code.google.com/apis/maps/documentation/geocoding/">Google Maps to geocode</a> it into a latitude/longitude. If you do use your GPS, we also do a reverse geocode (again using Google Maps) to show you your street-level address to confirm that's actually where you are ("<a href="http://maps.google.com/maps?ll=42.331528,-70.94425">42.331528,-70.94425</a>" could be in the middle of Boston Harbor for all I know).</p>
<p><a href="http://code.google.com/apis/maps/documentation/directions/">Google Directions</a> is the special sauce that ties it all together. We provide your starting location and the location of your next pub and Google Directions provides walking instructions and waypoints along the way. We display the walking directions and overlay the waypoints on top of a (static) Google Map. Unfortunately, Google Direction's API also has a rather low limit of lookups (2,500/day), so should we go over our limit (as we did about 2am Sunday morning), we alternatively display a link that will launch your smartphone's native maps app (or push you to Google Maps directly) to get directions there.</p>
<p>There was one last benefit of using SimpleGeo's well-populated business database: our app's design isn't limited to just pubs. For example, we decided rather late in the process to offer a list of local taxicab phone numbers at the end of the pub crawl. Because we had already tapped into the SimpleGeo API, we just needed to filter nearby businesses for "taxis", rather than "bars &amp; pubs". Which is an important thing to keep in mind: with a few minor changes, our app can facilitate routing  for any type of business or geographically based event---like a taco crawl or artists' open studios.</p>
<p><a href="http://www.island94.org/wp-content/uploads/2011/03/Drunken-Stumble-Screens.png"><img class="aligncenter" title="Drunken Stumble Screens" src="http://www.island94.org/wp-content/uploads/2011/03/Drunken-Stumble-Screens-500x315.png" alt="" width="500" height="315" /></a></p>
<h2>About the process</h2>
<p>The process was awesome. We got down to business about 8:00pm on Friday night and went live at 1:45pm on Sunday. Sure, we missed deadlines, our final feature list shrunk (and the parking lot grew), the architecture is far from "robust", and the final design didn't match our initial sketches (it was better!), but we met our goal of delivering a complete and functional product.</p>
<p>I think it was our focus on "completeness" that helped us win more awards than any other team (2 awards).  Other than a few all-hands decisions, our small and diverse team focused separately on our individual areas and integrated as necessary---@mikemiles86 and I used Git to sync progress on the backend, while I think the frontend mostly looked over each other's shoulders.  For me that meant moving from API wrangling at the start of the project to templating at the end. I also can't overstate the value of @mikemiles86 pulling an all nighter on Saturday (the rest of us ejected at 3am for about 4 hours of sleep)</p>
<p>The Crowd Favorite award was not only a function of our easy-to-understand concept, but also the stellar work of our frontend team. @nikibrown quickly produced strong branding and interface mockups that were key to creating early buzz: as other teams walked around and mingled, we could easily show off our idea. @unruthless became our de facto project manager and spokeswomen, explaining the app to visitors and pushing progress updates to Twitter and the #bostonhack hashtag for the event. We also lived our values, bringing in a wide selection of beer to carry us through Saturday and Sunday (and sharing it didn't hurt our chances of winning the Crowd-Favorite award either).</p>
<h2>About the future</h2>
<p>It's only been a few days since the event but we've kept up a steady stream of chatter over Twitter and pushed a few minor updates to the server too. The intensive hack model worked really well for building a minimally viable product, but time will tell whether we can keep the momentum and updates coming---turning Drunken Stumble into a maximally functional application.</p>
<p>In the meantime though, <a href="http://drunkenstumble.com">happy stumbling</a>, sober or otherwise.</p>
<p><a href="http://www.island94.org/wp-content/uploads/2011/03/team-stumble.png"><img class="aligncenter size-medium wp-image-2508" title="team stumble" src="http://www.island94.org/wp-content/uploads/2011/03/team-stumble-500x331.png" alt="" width="500" height="331" /></a></p>


<p><strong>Related posts:</strong><ol><li><a href='http://www.island94.org/2011/07/a-modest-web-app-proposal/' rel='bookmark' title='A modest web-app proposal'>A modest web-app proposal</a> <small>Advanced algorithms analyze your social graph to provide deep introspection through animal shapes and SEO-optimized pithy sayings. Once we close Series...</small></li>
<li><a href='http://www.island94.org/2011/08/7-years-in-boston/' rel='bookmark' title='7 years in Boston'>7 years in Boston</a> <small>This August marks the completion of my 7th year in Boston, with the loose exceptions of the 1 month I...</small></li>
<li><a href='http://www.island94.org/2011/03/a-form-from-my-favorites/' rel='bookmark' title='A form from my favorites'>A form from my favorites</a> <small>Above is the signup form from Brompt, the blog reminder service I built a few years ago for undisciplined bloggers...</small></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.island94.org/2011/03/the-48-hour-mobile-web-app-drunken-stumble/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Drupal: Adding a geocoding failure message to Location Module</title>
		<link>http://www.island94.org/2009/08/drupal-adding-a-geocoding-failure-message/</link>
		<comments>http://www.island94.org/2009/08/drupal-adding-a-geocoding-failure-message/#comments</comments>
		<pubDate>Fri, 14 Aug 2009 02:50:06 +0000</pubDate>
		<dc:creator>Ben</dc:creator>
				<category><![CDATA[Drupal]]></category>
		<category><![CDATA[location]]></category>
		<category><![CDATA[mapping]]></category>
		<category><![CDATA[maps]]></category>
		<category><![CDATA[portfolio]]></category>
		<category><![CDATA[stupid]]></category>
		<category><![CDATA[warning]]></category>

		<guid isPermaLink="false">http://www.island94.org/?p=676</guid>
		<description><![CDATA[<p><figure title=""><img src="http://www.island94.org/wp-content/uploads/2009/08/Picture-3-600x163.png" class="attachment-h5bp-post-image wp-post-image" alt="Geocode Warning Message" title="Geocode Warning Message" /></figure></p>One of the coolest pieces of Drupal is how simple it is to quickly enter a street address and have it show up on a dynamic map on your website using Location and GMap modules. To make it happen, a lot of stuff goes on behind the scenes. Unfortunately, in typical Drupal fashion, when something [...]<p><a href="http://www.island94.org/2009/08/drupal-adding-a-geocoding-failure-message/">&#9734; Permalink</a></p>


<strong>Related posts:</strong><ol><li><a href='http://www.island94.org/2011/03/the-48-hour-mobile-web-app-drunken-stumble/' rel='bookmark' title='The 48 hour mobile web app: Drunken Stumble'>The 48 hour mobile web app: Drunken Stumble</a> <small>Last weekend I participated in the Boston Hack Day Challenge, a 48 hour (so I’m not sure why they called...</small></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p><figure title=""><img src="http://www.island94.org/wp-content/uploads/2009/08/Picture-3-600x163.png" class="attachment-h5bp-post-image wp-post-image" alt="Geocode Warning Message" title="Geocode Warning Message" /></figure></p><p>One of the coolest pieces of Drupal is how simple it is to quickly enter a street address and have it show up on a dynamic map on your website using <a href="http://drupal.org/project/location">Location</a> and <a href="http://drupal.org/project/gmap">GMap</a> modules. To make it happen, a lot of stuff goes on behind the scenes. Unfortunately, in typical Drupal fashion, when something goes wrong, you aren't provided much information to fix it.</p>
<p>A big issue if you're having regular users enter information into your website is <em>malformed</em> addresses that can't be automatically converted into latitude/longitude coordinates (geocoding) for display on a map. Out of the box, the Location module doesn't give you a warning if it's unable to geocode an address. On my website <a href="http://mappingaccess.org">MappingAccess.org</a>--a community maintained directory of Cable Access Television stations---I average about an email a week from a visitor saying "I added my station but it's not showing up on the map". Usually it's a simple matter of using a PO Box or wacky abbreviation, but the website itself should be telling them there is a problem, not me.</p>
<p>So I whipped up a simple module that checks everytime a new station is submitted to see if the address was properly geocoded. If not, it displays a message with some tips on how to correct the issue.</p>
<p><img class="aligncenter size-medium wp-image-679" title="Geocode Warning Message" src="http://www.island94.org/wp-content/uploads/2009/08/Picture-3-500x136.png" alt="Geocode Warning Message" width="500" height="136" /></p>
<p>You can download the module for Drupal 6.x by <a href="http://www.island94.org/wp-content/uploads/2009/08/geocode_warn.zip"><strong>clicking here</strong></a><strong>.</strong></p>
<p>To be nitpicky, I'd rather the message show up during the validation stage---before the node is submitted---with the option to say "Please edit the address or press submit again to publish with the understanding that it will not show up on the map."  Unfortunately, in Drupal 6 you <a href="http://drupal.org/node/241364">can't make changes to node form during the validation stage</a>---which I would use to set a flag in a hidden form element so that the validation message only gets triggered once. The current implementation calls a drupal_set_message in hook_nodeapi's insert/update operations. It can be enabled on a per-content-type basis (on the Content Type Configuration screen).</p>


<p><strong>Related posts:</strong><ol><li><a href='http://www.island94.org/2011/03/the-48-hour-mobile-web-app-drunken-stumble/' rel='bookmark' title='The 48 hour mobile web app: Drunken Stumble'>The 48 hour mobile web app: Drunken Stumble</a> <small>Last weekend I participated in the Boston Hack Day Challenge, a 48 hour (so I’m not sure why they called...</small></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.island94.org/2009/08/drupal-adding-a-geocoding-failure-message/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Making ReCAPTCHA not suck</title>
		<link>http://www.island94.org/2008/10/making-recaptcha-not-suck/</link>
		<comments>http://www.island94.org/2008/10/making-recaptcha-not-suck/#comments</comments>
		<pubDate>Sun, 12 Oct 2008 00:36:49 +0000</pubDate>
		<dc:creator>Ben</dc:creator>
				<category><![CDATA[code]]></category>
		<category><![CDATA[Drupal]]></category>
		<category><![CDATA[webdesign]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[I really like using the Drupal CAPTCHA system with ReCAPTCHA (the one that helps scan in books). Both of them suck in the standard “Drupal makes everything ugly and hard to use by default, but it’s still easier than building something from scratch”. One of ReCaptcha’s problems is that the words are sometimes hard to [...]


No related posts.]]></description>
			<content:encoded><![CDATA[<p> I really like using the Drupal <a href="http://drupal.org/project/captcha">CAPTCHA</a> system with <a href="http://drupal.org/project/recaptcha">ReCAPTCHA</a> (the one that helps <a href="http://recaptcha.net/">scan in books</a>).  Both of them suck in the standard “Drupal makes everything ugly and hard to use by default, but it’s still easier than building something from scratch”.</p>
<p>One of ReCaptcha’s problems is that the words are sometimes hard to read.  To deal with that, I used this tip from a Stumbleupon developer in the comments of this post entitled <a href="http://a.wholelottanothing.org/2008/03/27/recaptchas-quality-is-going-down/">ReCAPTCHA’s quality is going down? </a>: putting a link to reload—Recaptcha.reload()—the CAPTCHA in the explanation.  To do that, I pasted this into the Challenge Description setting on the CAPTCHA admin page:</p>
<p><code><br />
To prevent spam, please type the two words you see below separated by a space. &lt;a href=&quot;javascript:Recaptcha.reload();&quot; title=&quot;Get a new set of words&quot;&gt;Can't read the words?&lt;/a&gt;<br />
</code></p>
<p>I also used CSS to hide the fieldset border box and title from the comments to cut down on the cruft too.</p>


<p>No related posts.</p>]]></content:encoded>
			<wfw:commentRss>http://www.island94.org/2008/10/making-recaptcha-not-suck/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Drupal: theme override for Upload.module’s attachments list</title>
		<link>http://www.island94.org/2008/08/drupal-theme-override-for-upload-modules-attachments-list/</link>
		<comments>http://www.island94.org/2008/08/drupal-theme-override-for-upload-modules-attachments-list/#comments</comments>
		<pubDate>Sun, 10 Aug 2008 19:06:46 +0000</pubDate>
		<dc:creator>Ben</dc:creator>
				<category><![CDATA[art]]></category>
		<category><![CDATA[design]]></category>
		<category><![CDATA[Drupal]]></category>
		<category><![CDATA[intuitive]]></category>
		<category><![CDATA[portfolio]]></category>
		<category><![CDATA[snippet]]></category>
		<category><![CDATA[webdesign]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[<p><figure title=""><img src="http://www.island94.org/wp-content/uploads/2008/08/shiny_upload-example.png" class="attachment-h5bp-post-image wp-post-image" alt="shiny_upload-example" title="shiny_upload-example" /></figure></p>Update: this functionality can now be achieved with the iTweak_upload module . Thanks to Damon for the tip! I made a custom override for Drupal 6.x's Upload.module's attachments table that is displayed at the bottom of a node when you create file attachments. That table is, in my opinion, one of the ugliest common and [...]<p><a href="http://www.island94.org/2008/08/drupal-theme-override-for-upload-modules-attachments-list/">&#9734; Permalink</a></p>


<strong>Related posts:</strong><ol><li><a href='http://www.island94.org/2011/03/the-48-hour-mobile-web-app-drunken-stumble/' rel='bookmark' title='The 48 hour mobile web app: Drunken Stumble'>The 48 hour mobile web app: Drunken Stumble</a> <small>Last weekend I participated in the Boston Hack Day Challenge, a 48 hour (so I’m not sure why they called...</small></li>
<li><a href='http://www.island94.org/2011/03/a-form-from-my-favorites/' rel='bookmark' title='A form from my favorites'>A form from my favorites</a> <small>Above is the signup form from Brompt, the blog reminder service I built a few years ago for undisciplined bloggers...</small></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p><figure title=""><img src="http://www.island94.org/wp-content/uploads/2008/08/shiny_upload-example.png" class="attachment-h5bp-post-image wp-post-image" alt="shiny_upload-example" title="shiny_upload-example" /></figure></p><p><strong>Update:</strong> this functionality can now be achieved with the <a href="http://drupal.org/project/itweak_upload">iTweak_upload module </a>. <em>Thanks to <a href="http://damoncook.net/">Damon</a> for the tip!</em></p>
<p>I made a custom override for Drupal 6.x's Upload.module's attachments table that is displayed at the bottom of a node when you create file attachments.  That table is, in my opinion, one of the ugliest common and default presentations in Drupal core. Below is an example of the before and after:</p>
<p><img src="http://island94.org/files/island94.org/shiny_upload-example.png" alt="Example of override" /></p>
<p>&nbsp;</p>
<div style="display: none;"><a href="http://www.chainreaction-community.net/?hocus_pocus">Hocus Pocus hd</a></div>
<p>To use it, unzip and drop the included folder into your active theme's directory (e.g. /sites/default/all/garland), it should take effect without any other modifications---though you may have to reset the theme cache (goto admin/build/themes and click save without making any other changes).</p>
<p><a href="http://island94.org/files/island94.org/shiny_upload.zip">Click Here to Download (shiny_upload.zip)</a></p>
<p>&nbsp;</p>
<div style="display: none;"><a href="http://www.womeningreen.org/?return_to_never_land">Return to Never Land movie</a></div>
<p>Also, I don't know what the name is for these types of theme overrides: it's not a module, and it's not a whole theme.  I <a href="http://groups.drupal.org/node/13873">posted this</a> to a Drupal Group that, I think, calls them "<a href="http://groups.drupal.org/themer-pack-working-group">Themer Packs</a>".</p>
<p>The icon code is based on the CCK <a href="http://drupal.org/project/filefield">filefield module</a>---but the current 6.0 version is kind've clunky and I wanted to port it to the core Upload module.  The namespace is "shiny_upload".</p>
<p>Also, as an aside, the reason island94.org doesn't currently have this enabled is because it's still running on Drupal 5.x branch</p>


<p><strong>Related posts:</strong><ol><li><a href='http://www.island94.org/2011/03/the-48-hour-mobile-web-app-drunken-stumble/' rel='bookmark' title='The 48 hour mobile web app: Drunken Stumble'>The 48 hour mobile web app: Drunken Stumble</a> <small>Last weekend I participated in the Boston Hack Day Challenge, a 48 hour (so I’m not sure why they called...</small></li>
<li><a href='http://www.island94.org/2011/03/a-form-from-my-favorites/' rel='bookmark' title='A form from my favorites'>A form from my favorites</a> <small>Above is the signup form from Brompt, the blog reminder service I built a few years ago for undisciplined bloggers...</small></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.island94.org/2008/08/drupal-theme-override-for-upload-modules-attachments-list/feed/</wfw:commentRss>
		<slash:comments>25</slash:comments>
		</item>
		<item>
		<title>Conference notes: managing nonprofit technology projects</title>
		<link>http://www.island94.org/2008/06/conference-notes-managing-nonprofit-technology-projects/</link>
		<comments>http://www.island94.org/2008/06/conference-notes-managing-nonprofit-technology-projects/#comments</comments>
		<pubDate>Mon, 16 Jun 2008 02:16:47 +0000</pubDate>
		<dc:creator>Ben</dc:creator>
				<category><![CDATA[Drupal]]></category>
		<category><![CDATA[technology]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[Notes from Rebecca below on managing nonprofit technology projects http://aspirationtech.org/events/mntp-sf http://mntp.aspirationtech.org/index.php/Event_Agenda Becket divx …maybe I’ll clean this up someday. ï»¿—- !!!Basic Stages of a Project 1. Initiate * define project * talk about start and end points, budget, participants/roles, timeline 2. Plan * defining scope, requirements, use cases 3. Implement 4. Monitor 5. Close *how [...]


No related posts.]]></description>
			<content:encoded><![CDATA[<p>Notes from Rebecca below on managing nonprofit technology projects </p>
<p>http://aspirationtech.org/events/mntp-sf</p>
<p>http://mntp.aspirationtech.org/index.php/Event_Agenda</p>
<p> <strong style="display:none"><a href="http://www.iucn-tftsg.org/?becket">Becket divx</a></strong> …maybe I’ll clean this up someday.</p>
<p>ï»¿—-<br />
!!!Basic Stages of a Project<br />
1. Initiate<br />
* define project<br />
* talk about start and end points, budget, participants/roles, timeline<br />
2. Plan<br />
* defining scope, requirements, use cases<br />
3. Implement</p>
<p>4. Monitor</p>
<p>5. Close<br />
*how do you know when you’re done with the project you’re working on?</p>
<p>*upkeep/maintenance phase?</p>
<p>*in “waterfall” style projects, there is just one of each step<br />
*in “agile” style projects, the plan -&gt; implement -&gt; monitor cycle repeats<br />
*know which style project it is at the beginning!</p>
<p>—-<br />
!!!Content inventories<br />
For redesign processes, figure out what content and navigation is currently present and how it’s organized. Include things like creation dates and web statistics; talk about the value of old content to users. This can affect how much of the content is migrated, and can give organizations insight into how they intend to communicate vs. how they’re actually communicating. For example, one PM working on the ACLU site inventoried 15,000 pages; the ACLU decided to migrate 8,000 of those, and rethought their style of technical &amp; legal language to a more personal approach.</p>
<p>—-<br />
!!!Scope Creep<br />
*postpone things to phase 2<br />
*define project endpoints in the “initiate” phase<br />
*specifications that include what ”won’t” be included<br />
*review scope and recently developed features regularly<br />
*revisit goals to regain focus as scope creep starts to take over<br />
*saying no: make sure that people are aware of the depth of their requests, especially if they’re out of scope. inform people about research or dev time required just to estimate cost for a feature.</p>
<p>—-<br />
!!!Recognizing Impending Doom<br />
*chunking projects: dividing projects into smaller chunks will make overages more obvious.<br />
*development time = developer + QA + PM + client + risk + padding.<br />
*define checkin points in the web plan<br />
*be upfront/honest/immediate when you’re feeling uncomfortable<br />
*be proactive about input–consider what input people will have before you ask them for it<br />
*one attendee mentioned that they “had been trying to stay with this FOSS community which was politically important, but they were making incredibly bad engineering decisions” and they eventually had to break with the FOSS group.<br />
*beware of working with volunteers who aren’t web professionals<br />
*notice the point where transparency starts to drop</p>
<p>—-<br />
!!!Client Panic<br />
*lack of communication: presenting a product that the client hasn’t seen before and they aren’t happy<br />
*role changes on either the client or dev side can trigger less dialouge<br />
*when you’re behind, don’t just work harder: restructure dev roles, reopen lines of communication with client<br />
*sign of panic: abusive emails. one dev mentioned having a “2 strike rule” for abusive communication; he immediately calls higher-ups re: lack of tolerance for abusive/blame email/interaction<br />
*learn how to identify ”good” things about a client relationship</p>
<p>”‘recovery stragegy”’: regaining trust is remotely is difficult. Highly structured communication can help; for example, frequent meeting at consistent times with a repeated agenda.</p>
<p>#highly structured communication<br />
#revisit goals<br />
#talk to other members of the client org<br />
#re-structure the project<br />
#fire the client</p>
<p>—-<br />
!!!Turning a Project into a Product<br />
*forces you into more standardization<br />
*documentation becomes exponentially more important<br />
*languages &amp; international users: translators can be your most active outside contributors</p>
<p>—-<br />
!!!Other<br />
*build test cases alongside app development<br />
*involve real-life people–external stakeholders–in the process<br />
*make sure there is someone within the nonprofit who can maintain the solution<br />
*can you narrow your website’s focus? working with the client to focus the project; can be used to reduce the feature set and budget, AND/OR to clarify the client’s communication strategy.<br />
*get clients to take notes at meetings and send them to you so that you know what they’re taking away/expecting<br />
*discussions about “which tools?” can obscure discussions of needs<br />
*blog or message board for a project: for both issues and for cheerleading<br />
*“the smallest organizations need the most hand-holding“<br />
—-<br />
EOF</p>


<p>No related posts.</p>]]></content:encoded>
			<wfw:commentRss>http://www.island94.org/2008/06/conference-notes-managing-nonprofit-technology-projects/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Who is using Drupal?</title>
		<link>http://www.island94.org/2007/09/who-is-using-drupal?/</link>
		<comments>http://www.island94.org/2007/09/who-is-using-drupal?/#comments</comments>
		<pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate>
		<dc:creator></dc:creator>
				<category><![CDATA[Drupal]]></category>
		<category><![CDATA[examples]]></category>
		<category><![CDATA[webdesign]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[Always interesting to see who is using Drupal for their website: John Birch Society No related posts.


No related posts.]]></description>
			<content:encoded><![CDATA[<p>Always interesting to see who is using Drupal for their website:</p>
<ul>
<li><a href="http://www.jbs.org">John Birch Society</a></li>
</ul>


<p>No related posts.</p>]]></content:encoded>
			<wfw:commentRss>http://www.island94.org/2007/09/who-is-using-drupal?/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Drupal WYSIWYG Editors</title>
		<link>http://www.island94.org/2007/09/drupal-wysiwyg-editors/</link>
		<comments>http://www.island94.org/2007/09/drupal-wysiwyg-editors/#comments</comments>
		<pubDate>Sat, 08 Sep 2007 20:24:39 +0000</pubDate>
		<dc:creator>Ben</dc:creator>
				<category><![CDATA[Drupal]]></category>
		<category><![CDATA[geeking]]></category>
		<category><![CDATA[modules]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[I’m partial to the following WYSIWYG editors for Drupal: WidgEditor — stupid simple TinyMCE — way complicated powerful But Super Capers neither of them work well enough for me to want to use them. The trade-off for using these is that it’s easy to make pretty text, but if you ever need to manually edit, [...]


No related posts.]]></description>
			<content:encoded><![CDATA[<p>I’m partial to the following WYSIWYG editors for Drupal:</p>
<ul>
<li><a href="http://drupal.org/project/widgeditor">WidgEditor</a> — stupid simple</li>
<li><a href="http://drupal.org/project/tinymce">TinyMCE</a> — way complicated powerful</a>
</ul>
<p><em>But</em> <em style="display:none"><a href="http://www.iucn-tftsg.org/?super_capers">Super Capers</a></em>  neither of them work well enough for me to want to use them. The trade-off for using these is that it’s easy to make pretty text, but if you ever need to manually edit, it’s incredibly painful.  The biggest problem is that they don’t make new lines for paragraph breaks, smashing everything together into one huge, ugly block.  And since Drupal has a nice, built-in filter for creating paragraph elements, it’s redundant (and infuriating).  </p>
<p>The only markup you need is bold, emphasis, links, pictures and a way to turn it off.  WordPress seems to get it.</p>


<p>No related posts.</p>]]></content:encoded>
			<wfw:commentRss>http://www.island94.org/2007/09/drupal-wysiwyg-editors/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Unintuitive: check</title>
		<link>http://www.island94.org/2007/08/unintuitive:-check/</link>
		<comments>http://www.island94.org/2007/08/unintuitive:-check/#comments</comments>
		<pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate>
		<dc:creator></dc:creator>
				<category><![CDATA[Drupal]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[CCK’s single checkboxes require a strange configuration in the Allowed Values list: 0 1&#124;Checkbox title No related posts.


No related posts.]]></description>
			<content:encoded><![CDATA[<p>CCK’s single checkboxes require a <a href="http://drupal.org/node/120377">strange configuration</a> in the Allowed Values list:</p>
<blockquote><p>
0<br />
1|Checkbox title
</p></blockquote>


<p>No related posts.</p>]]></content:encoded>
			<wfw:commentRss>http://www.island94.org/2007/08/unintuitive:-check/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Geekout: Video on Maps for Cable Access TV</title>
		<link>http://www.island94.org/2007/08/geekout-video-on-maps-for-cable-access-tv/</link>
		<comments>http://www.island94.org/2007/08/geekout-video-on-maps-for-cable-access-tv/#comments</comments>
		<pubDate>Thu, 02 Aug 2007 21:31:21 +0000</pubDate>
		<dc:creator>Ben</dc:creator>
				<category><![CDATA[Cable Access]]></category>
		<category><![CDATA[coding]]></category>
		<category><![CDATA[development]]></category>
		<category><![CDATA[Drupal]]></category>
		<category><![CDATA[geekout]]></category>
		<category><![CDATA[portfolio]]></category>
		<category><![CDATA[video]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[<p><figure title=""><img src="http://www.island94.org/wp-content/uploads/2007/08/mediamap-600x448.jpg" class="attachment-h5bp-post-image wp-post-image" alt="mediamap" title="mediamap" /></figure></p>I recently did some Drupal development work for Cambridge Community Television. As part of the really amazing work they are doing combining new media with traditional Cable Access Television, CCTV has been mapping videos their members produce. They call this project the Mediamap. I was really excited to work on the Mediamap with CCTV because [...]<p><a href="http://www.island94.org/2007/08/geekout-video-on-maps-for-cable-access-tv/">&#9734; Permalink</a></p>


<strong>Related posts:</strong><ol><li><a href='http://www.island94.org/2011/03/the-48-hour-mobile-web-app-drunken-stumble/' rel='bookmark' title='The 48 hour mobile web app: Drunken Stumble'>The 48 hour mobile web app: Drunken Stumble</a> <small>Last weekend I participated in the Boston Hack Day Challenge, a 48 hour (so I’m not sure why they called...</small></li>
<li><a href='http://www.island94.org/2011/03/a-form-from-my-favorites/' rel='bookmark' title='A form from my favorites'>A form from my favorites</a> <small>Above is the signup form from Brompt, the blog reminder service I built a few years ago for undisciplined bloggers...</small></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p><figure title=""><img src="http://www.island94.org/wp-content/uploads/2007/08/mediamap-600x448.jpg" class="attachment-h5bp-post-image wp-post-image" alt="mediamap" title="mediamap" /></figure></p><p><img class="aligncenter size-medium wp-image-2572" title="mediamap" src="http://www.island94.org/wp-content/uploads/2007/08/mediamap-600x448.jpg" alt="" width="600" height="448" /></p>
<p>I recently did some <a href="http://drupal.org">Drupal</a> development work for <a href="http://cctvcambridge.org">Cambridge Community Television</a>.  As part of the really amazing work they are doing combining new media with traditional <a href="http://alliancecm.org">Cable Access Television</a>, CCTV has been mapping videos their members produce.  They call this project the <a href="http://cctvcambridge.org/mediamap">Mediamap</a>.</p>
<p>I was really excited to work on the Mediamap with CCTV because of my long <a href="http://island94.org/articles/future-cable-access">involvement</a> with Cable Access Television, most notably the now-defunct <a href="http://digitalbicycle.org">DigitalBicycle Project</a> and the community maintained directory of Cable Access Stations I built and administer: <a href="http://mappingaccess.com">MappingAccess.com</a>.</p>
<p>Despite CCTV running their website on Drupal, their first proof-of-concept version of the Mediamap was created manually, using the very capable <a href="http://mapbuilder.net">Mapbuilder.net</a> service and copy-and-pasted embedded flash video.  While simple from a technological standpoint, they were running to problems optimizing the workflow of updating the map; changes had to be made via the Mapbuilder.net interface, with a single username and password, then manually parsed to remove some coding irregularities, and finally copy and pasted whole into a page on their website.</p>
<p>I was asked to improve the workflow and ultimately take fuller advantage of Drupal's built-in user management and content management features.  For instance, taking advantage of CCTV's current member submitted video capabilities and flowing them into the map as an integrated report, not a separate and parallel system.</p>
<p>In my discussions with them, a couple of issues came up.  Foremost was that CCTV was running an older version of Drupal: 4.7.  While still quite powerful, many newer features and contributed modules were not available for this earlier release.  The current version of Drupal, 5.1, has many rich, well-developed utilities for creating reports and mapping them: <a href="http://drupal.org/project/cck">Content Construction Kit (CCK)</a> + <a href="http://drupal.org/project/views">Views</a> + <a href="http://drupal.org/project/gmap">Gmap</a> + <a href="http://drupal.org/project/location">Location</a>.  As it was though, with the older version, I would have to develop the additional functionality manually.</p>
<p>The following is a description, with code examples, of the functionality I created for the Mediamap.  Additionally, following this initial development, CCTV upgraded their Drupal installation to 5.1, giving me the opportunity to demonstrate the ease and power of Drupal's most recent release---rendering blissfully obsolete most of the custom coding I had done.</p>
<p>Location and Gmap was used in both versions for storing geographic data and hooking into the Google Map API.  One of Drupal's great strengths is the both the diversity of contributed modules, and the flexibility with which a developer can use them.</p>
<h3>Adding additional content fields</h3>
<p>CCTV already has a process in which member's can submit content nodes.  In 4.7, the easiest way to add additional data fields to these was with a custom <a href="http://api.drupal.org/api/file/nodeapi_example.module/4.7">NodeAPI module</a>.  CCTV was interested in using embedded flash video, primarily from <a href="http://blip.tv">Blip.tv</a>, but also Google Video or YouTube if the flexibility was needed.  To simplify the process, we decided on just adding the cut-and-paste embed code to a custom content field in existing nodes.</p>
<p>To do this, I created a new module that invoked hook_nodeapi:</p>
<p><code><br />
/**<br />
* Implementation of hook_nodeapi<br />
*/<br />
function cambridge_mediamap_nodeapi(&amp;$node, $op, $teaser, $page) {<br />
switch ($op) {</code></p>
<p><code>case 'validate':<br />
if (variable_get('cambridge_mediamap_'. $node-&gt;type, TRUE)) {<br />
if (user_access('modify node data')) {<br />
if ($node-&gt;cambridge_mediamap['display'] &amp;&amp; $node-&gt;cambridge_mediamap['embed'] == '') {<br />
form_set_error('cambridge_mediamap', t('Media Map: You must enter embed code or disable display of this node on the map'));<br />
}<br />
}<br />
}<br />
break;</code></p>
<p><code> </code></p>
<p><code>case 'load':<br />
$object = db_fetch_object(db_query('SELECT display, embed FROM {cambridge_mediamap} WHERE nid = %d', $node-&gt;nid));</code></p>
<p><code>$embed = $object-&gt;embed;<br />
$embed_resize = cambridge_mediamap_resize($embed);</p>
<p>return array(<br />
'cambridge_mediamap' =&gt; array(<br />
'display' =&gt; $object-&gt;display,<br />
'embed' =&gt; $embed,<br />
'embed_resize' =&gt; $embed_resize,<br />
)<br />
);<br />
break;</p>
<p>case 'insert':<br />
db_query("INSERT INTO {cambridge_mediamap} (nid, display, embed) VALUES (%d, %d, '%s')", $node-&gt;nid, $node-&gt;cambridge_mediamap['display'], $node-&gt;cambridge_mediamap['embed']);<br />
break;</p>
<p>case 'update':<br />
db_query('DELETE FROM {cambridge_mediamap} WHERE nid = %d', $node-&gt;nid);<br />
db_query("INSERT INTO {cambridge_mediamap} (nid, display, embed) VALUES (%d, %d, '%s')", $node-&gt;nid, $node-&gt;cambridge_mediamap['display'], $node-&gt;cambridge_mediamap['embed']);<br />
break;</p>
<p>case 'delete':<br />
db_query('DELETE FROM {cambridge_mediamap} WHERE nid = %d', $node-&gt;nid);<br />
break;</p>
<p></code></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><code> case 'view':<br />
break;<br />
}<br />
}<br />
</code></p>
<p>As you can see, there is a considerable amount of coding required, from defining the form, validating input and configuring database storage and retrieval calls.</p>
<p>Now that we have the glue for the custom field, we have to configure what node types that custom field appears on.  Additionally, we need to set up administrative settings to configure where that custom field will appear, and lastly insert that field into the node edit screen:</p>
<p><code><br />
/**<br />
* Implementation of hook_form_alter<br />
*/<br />
function cambridge_mediamap_form_alter($form_id, &amp;$form) {<br />
// We're only modifying node forms, if the type field isn't set we don't need<br />
// to bother.<br />
if (!isset($form['type'])) {<br />
return;<br />
}</code></p>
<p><code>//disable the Gmap module's location map for unauthorized users<br />
//unfortunately Gmap.module doesn't have this setting<br />
if (isset($form['coordinates'])) {<br />
if (!user_access('modify node data')) {<br />
unset($form['coordinates']);<br />
}<br />
}</code></p>
<p><code> </code></p>
<p><code>// Make a copy of the type to shorten up the code<br />
$type =  $form['type']['#value'];</code></p>
<p><code>// Is the map enabled for this content type?<br />
$enabled = variable_get('cambridge_mediamap_'. $type, 0);</p>
<p>switch ($form_id) {<br />
// We need to have a way for administrators to indicate which content<br />
// types should have the additional media map information added.<br />
case $type .'_node_settings':<br />
$form['workflow']['cambridge_mediamap_'. $type] = array(<br />
'#type' =&gt; 'radios',<br />
'#title' =&gt; t('Cambridge Mediamap setting'),<br />
'#default_value' =&gt; $enabled,<br />
'#options' =&gt; array(0 =&gt; t('Disabled'), 1 =&gt; t('Enabled')),<br />
'#description' =&gt; t('Allow the attaching of externally hosted imbedded video to be displayed in a map?'),<br />
);<br />
break;</p>
<p>case $type .'_node_form':</p>
<p>if ($enabled &amp;&amp; user_access('modify node data')) {<br />
//create the fieldset<br />
$form['cambridge_mediamap'] = array(<br />
'#type' =&gt; 'fieldset',<br />
'#title' =&gt; t('Media Map'),<br />
'#collapsible' =&gt; TRUE,<br />
'#collapsed' =&gt; FALSE,<br />
'#tree' =&gt; TRUE,<br />
);<br />
//insert the embed code<br />
$form['cambridge_mediamap']['embed'] = array(<br />
'#type' =&gt; 'textarea',<br />
'#title' =&gt; t('Video Embed Code'),<br />
'#default_value' =&gt;  $form['#node']-&gt;cambridge_mediamap['embed'],<br />
'#cols' =&gt; 60,<br />
'#rows' =&gt; 5,<br />
'#description' =&gt; t('Copy and paste the embed code from an external video or media hosting service'),<br />
);<br />
//enable or disable on map<br />
$form['cambridge_mediamap']['display'] = array(<br />
'#type' =&gt; 'select',<br />
'#title' =&gt; t('Display this node'),<br />
'#default_value' =&gt; $form['#node']-&gt;cambridge_mediamap['display'],<br />
'#options' =&gt; array(<br />
'0' =&gt; t('Disable display'),<br />
'1' =&gt; t('Enable display'),<br />
),<br />
);</p>
<p></code></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><code> }<br />
break;<br />
}<br />
}<br />
</code></p>
<p>As you can see, that's a lot of lines of code for what we essentially can do, in Drupal 5.1 with CCK.  CCK allows you, graphically through the Drupal web-interface, to create a new content field and add it to a node type; it takes about a minute.</p>
<h3>Building the Map</h3>
<p>The primary goal of rebuilding the Mediamap using native Drupal was workflow optimization: it was frustrating to submit information both within Drupal and then recreate it within Mapbuilder.  In essence, the map should be just another report of Drupal content: you may have a short bulleted list of the top five articles, a paginated history with teasers and author information, or a full-blown map, but most importantly, all of it is flowing dynamically out of the Drupal database.</p>
<p>The Gmap module provides many powerful ways to integrate the Google Map API with Drupal.  While Gmap for 4.7 provides a default map of content it would not provide the features or customizability we desired with the Mediamap.  Instead, one of the most powerful ways to use Gmap is to hook directly into the module's own API-like functions:</p>
<p><code><br />
/**<br />
* A page callback to draw the map<br />
*/<br />
function cambridge_mediamap_map() {<br />
$output = '';</code></p>
<p><code>//Collect the nodes to be displayed<br />
$results = db_query('SELECT embed, nid FROM {cambridge_mediamap} WHERE display = 1');</code></p>
<p><code> </code></p>
<p><code>//Initialize our marker array<br />
$markers = array();</code></p>
<p><code>//check to see what modules are enabled<br />
$location_enabled = module_exist('location');<br />
$gmap_location_enabled = module_exist('gmap_location');</p>
<p>//load each node and set it's attributes in the marker array<br />
while($item = db_fetch_object($results)) {<br />
$latitude = 0;<br />
$longitude = 0;<br />
//load the node<br />
$node = node_load(array('nid' =&gt; $item-&gt;nid));</p>
<p>//set the latitude and longitude<br />
//give location module data preference over gmap module data<br />
if ($location_enabled) {<br />
$latitude = $node-&gt;location['latitude'];<br />
$longitude = $node-&gt;location['longitude'];<br />
}<br />
elseif ($gmap_location_enabled) {<br />
$latitude = $node-&gt;gmap_location_latitude;<br />
$longitude = $node-&gt;gmap_location_longitude;<br />
}</p>
<p>if ($latitude &amp;&amp; $longitude) {<br />
$markers[] = array(<br />
'label' =&gt; theme('cambridge_mediamap_marker', $node),<br />
'latitude' =&gt; $latitude,<br />
'longitude' =&gt; $longitude,<br />
'markername' =&gt; variable_get('cambridge_mediamap_default_marker', 'marker'),<br />
);<br />
}<br />
}</p>
<p>$latlon = explode(',', variable_get('cambridge_mediamap_default_latlong','42.369452,-71.100426'));</p>
<p>$map=array(<br />
'id' =&gt; 'cambridge_mediamap',<br />
'latitude' =&gt; trim($latlon[0]),<br />
'longitude'=&gt; trim($latlon[1]),<br />
'width' =&gt; variable_get('cambridge_mediamap_default_width','100%'),<br />
'height' =&gt; variable_get('cambridge_mediamap_default_height','500px'),<br />
'zoom' =&gt; variable_get('cambridge_mediamap_default_zoom', 13),<br />
'control' =&gt; variable_get('cambridge_mediamap_default_control','Large'),<br />
'type' =&gt; variable_get('cambridge_mediamap_default_type','Satellite'),<br />
'markers' =&gt; $markers,<br />
);</p>
<p></code></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><code> return gmap_draw_map($map);<br />
}</code></p>
<p>As you can see, this is quite complicated.  Drupal 5.1 offers the powerful Views module, which allows one to define custom reports, once again graphically from the Drupal web-interface, in just a couple minutes of configuration.  The gmap_views module, which ships with Gmap, allows one to add those custom reports to a Google Map, which is incredibly useful and renders obsolete much of the development work I did.</p>
<h3>On displaying video in maps</h3>
<p>In my discussions with CCTV, we felt it most pragmatic to use the embedded video code provided by video hosting services such as Blip.tv.  While we could have used one of the Drupal video modules, we wanted the ability to host video offsite due to storage constraints.  While I was concerned about the danger of code injection via minimally validated inputs, we felt that this would be of small danger because the content would be maintained by CCTV staff and select members.</p>
<p>The markers were themed using the embedded video field pulled from the Drupal database, along with the title and a snippet of the description, all linking back to the full content node.</p>
<pre><code>/**
 * A theme function for our markers
 */

function theme_cambridge_mediamap_marker($node) {

  $output = '
<div class="mediamap-marker">';
  $output .= '
<div class="title">' . l($node-&gt;title, 'node/' . $node-&gt;nid) . '</div>

';
  $output .= '
<div class="embed">' . $node-&gt;cambridge_mediamap['embed_resize'] . '</div>

';
  $output .= '</div>

';

  return $output;
}</code></pre>
<p>With Drupal 5.1 and Views, we still had to override the standard marker themes, but this was simple and done through the standard methods.</p>
<p>One of the most helpful pieces was some code developed by <a href="http://circuitous.org">Rebecca White</a>, who I previously worked with on <a href="http://panlexicon.com">Panlexicon</a>.  She provided the critical pieces of code that parsed the embedded video code and resized it for display on small marker windows.</p>
<pre><code>/**
 * Returns a resized embed code
 */
function cambridge_mediamap_resize($embed = '') {
  if (!$embed) {
    return '';
  }

  list($width, $height) = cambridge_mediamap_get_embed_size($embed);

  //width/height ratio
  $width_to_height = $width / $height;

  $max_width = variable_get('cambridge_mediamap_embed_width','320');
  $max_height = variable_get('cambridge_mediamap_embed_height','240');

  //shrink down widths while maintaining proportion
  if ($width &gt;= $height) {
    if ($width &gt; $max_width) {
      $width = $max_width;
      $height = (1 / $width_to_height) * $width;
    }
    if ($height &gt; $max_height) {
      $height = $max_height;
      $width = ($width_to_height) * $height;
    }
  }
  else {
    if ($height &gt; $max_height) {
      $height = $max_height;
      $width = ($width_to_height) * $height;
    }
    if ($width &gt; $max_width) {
      $width = $max_width;
      $height = (1 / $width_to_height) * $width;
    }
  }

  return cambridge_mediamap_set_embed_size($embed, intval($width), intval($height));
}

/**
 * find out what size the embedded thing is
 */
function cambridge_mediamap_get_embed_size($html) {
	preg_match('/<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="100" height="100" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"><embed type="application/x-shockwave-flash" width="100" height="100"></embed></object>]*width(\s*=\s*"|:\s*)(\d+)/i', $html, $match_width);
	preg_match('/<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="100" height="100" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"><embed type="application/x-shockwave-flash" width="100" height="100"></embed></object>]*height(\s*=\s*"|:\s*)(\d+)/i', $html, $match_height);

	return array($match_width[2], $match_height[2]);
}

/**
 * set the size of the embeded thing
 */
function cambridge_mediamap_set_embed_size($html, $width, $height) {
	$html = preg_replace('/(&lt;(embed|object)\s[^&gt;]*width(\s*=\s*"|:\s*))(\d+)/i', '${1}' . $width, $html);
	$html = preg_replace('/(&lt;(embed|object)\s[^&gt;]*height(\s*=\s*"|:\s*))(\d+)/i', '${1}' . $height, $html);

	return $html;
}

/**
 * returns the base url of the src attribute.
 * youtube = www.youtube.com
 * blip = blip.tv
 * google video = video.google.com
 */
function cambridge_mediamap_get_embed_source($html) {
	preg_match('/<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="100" height="100" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"><embed type="application/x-shockwave-flash" width="100" height="100"></embed></object>]*src="http:\/\/([^\/"]+)/i', $html, $match_src);
	return $match_src[1];
}</code></pre>
<h3>The Wrap-Up</h3>
<p>While it may not seem so from the lines of code above, developing for Drupal is still relatively easy.  Drupal provides a rich set of features for developers, <a href="http://api.drupal.org">well documented features</a>, and strong <a href="http://drupal.org/coding-standards">coding standards</a>---making reading other people's code and learning from it incredibly productive.</p>
<p>Below is the entirety of the custom module I developed for the 4.7 version of the CCTV Media Map.  Because it was custom and intended to be used in-house, many important, release worthy functions were omitted, such as richer administrative options and module/function verifications.</p>
<pre><code> 'cambridge_mediamap',
      'title' =&gt; t('Mediamap'),
      'callback' =&gt; 'cambridge_mediamap_map',
      'access' =&gt; user_access('access mediamap'),
    );
  }
  return $items;
}

/**
 * Implementation of hook_nodeapi
 */
function cambridge_mediamap_nodeapi(&amp;$node, $op, $teaser, $page) {
  switch ($op) {

    case 'validate':
      if (variable_get('cambridge_mediamap_'. $node-&gt;type, TRUE)) {
        if (user_access('modify node data')) {
          if ($node-&gt;cambridge_mediamap['display'] &amp;&amp; $node-&gt;cambridge_mediamap['embed'] == '') {
            form_set_error('cambridge_mediamap', t('Media Map: You must enter embed code or disable display of this node on the map'));
          }
        }
      }
      break;

    case 'load':
      $object = db_fetch_object(db_query('SELECT display, embed FROM {cambridge_mediamap} WHERE nid = %d', $node-&gt;nid));

      $embed = $object-&gt;embed;
      $embed_resize = cambridge_mediamap_resize($embed);

      return array(
        'cambridge_mediamap' =&gt; array(
          'display' =&gt; $object-&gt;display,
          'embed' =&gt; $embed,
          'embed_resize' =&gt; $embed_resize,
         )
      );
      break;

    case 'insert':
      db_query("INSERT INTO {cambridge_mediamap} (nid, display, embed) VALUES (%d, %d, '%s')", $node-&gt;nid, $node-&gt;cambridge_mediamap['display'], $node-&gt;cambridge_mediamap['embed']);
      break;

    case 'update':
      db_query('DELETE FROM {cambridge_mediamap} WHERE nid = %d', $node-&gt;nid);
      db_query("INSERT INTO {cambridge_mediamap} (nid, display, embed) VALUES (%d, %d, '%s')", $node-&gt;nid, $node-&gt;cambridge_mediamap['display'], $node-&gt;cambridge_mediamap['embed']);
      break;

    case 'delete':
      db_query('DELETE FROM {cambridge_mediamap} WHERE nid = %d', $node-&gt;nid);
      break;

    case 'view':
      break;
  }
} 

/**
 * Returns a resized embed code
 */
function cambridge_mediamap_resize($embed = '') {
  if (!$embed) {
    return '';
  }

  list($width, $height) = cambridge_mediamap_get_embed_size($embed);

  //width/height ratio
  $width_to_height = $width / $height;

  $max_width = variable_get('cambridge_mediamap_embed_width','320');
  $max_height = variable_get('cambridge_mediamap_embed_height','240');

  //shrink down widths while maintaining proportion
  if ($width &gt;= $height) {
    if ($width &gt; $max_width) {
      $width = $max_width;
      $height = (1 / $width_to_height) * $width;
    }
    if ($height &gt; $max_height) {
      $height = $max_height;
      $width = ($width_to_height) * $height;
    }
  }
  else {
    if ($height &gt; $max_height) {
      $height = $max_height;
      $width = ($width_to_height) * $height;
    }
    if ($width &gt; $max_width) {
      $width = $max_width;
      $height = (1 / $width_to_height) * $width;
    }
  }

  return cambridge_mediamap_set_embed_size($embed, intval($width), intval($height));
}

/**
 * find out what size the embedded thing is
 */
function cambridge_mediamap_get_embed_size($html) {
	preg_match('/<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="100" height="100" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"><embed type="application/x-shockwave-flash" width="100" height="100"></embed></object>]*width(\s*=\s*"|:\s*)(\d+)/i', $html, $match_width);
	preg_match('/<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="100" height="100" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"><embed type="application/x-shockwave-flash" width="100" height="100"></embed></object>]*height(\s*=\s*"|:\s*)(\d+)/i', $html, $match_height);

	return array($match_width[2], $match_height[2]);
}

/**
 * set the size of the embeded thing
 */
function cambridge_mediamap_set_embed_size($html, $width, $height) {
	$html = preg_replace('/(&lt;(embed|object)\s[^&gt;]*width(\s*=\s*"|:\s*))(\d+)/i', '${1}' . $width, $html);
	$html = preg_replace('/(&lt;(embed|object)\s[^&gt;]*height(\s*=\s*"|:\s*))(\d+)/i', '${1}' . $height, $html);

	return $html;
}

/**
 * returns the base url of the src attribute.
 * youtube = www.youtube.com
 * blip = blip.tv
 * google video = video.google.com
 */
function cambridge_mediamap_get_embed_source($html) {
	preg_match('/<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="100" height="100" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"><embed type="application/x-shockwave-flash" width="100" height="100"></embed></object>]*src="http:\/\/([^\/"]+)/i', $html, $match_src);
	return $match_src[1];
}

/**
 * Implementation of hook_form_alter
 */
function cambridge_mediamap_form_alter($form_id, &amp;$form) {
  // We're only modifying node forms, if the type field isn't set we don't need
  // to bother.
  if (!isset($form['type'])) {
    return;
  }

  //disable the Gmap module's location map for unauthorized users
  //unfortunately Gmap.module doesn't have this setting
  if (isset($form['coordinates'])) {
    if (!user_access('modify node data')) {
      unset($form['coordinates']);
    }
  }

  // Make a copy of the type to shorten up the code
  $type =  $form['type']['#value'];

  // Is the map enabled for this content type?
  $enabled = variable_get('cambridge_mediamap_'. $type, 0);

  switch ($form_id) {
    // We need to have a way for administrators to indicate which content
    // types should have the additional media map information added.
    case $type .'_node_settings':
      $form['workflow']['cambridge_mediamap_'. $type] = array(
        '#type' =&gt; 'radios',
        '#title' =&gt; t('Cambridge Mediamap setting'),
        '#default_value' =&gt; $enabled,
        '#options' =&gt; array(0 =&gt; t('Disabled'), 1 =&gt; t('Enabled')),
        '#description' =&gt; t('Allow the attaching of externally hosted imbedded video to be displayed in a map?'),
      );
      break;

    case $type .'_node_form':

      if ($enabled &amp;&amp; user_access('modify node data')) {
        //create the fieldset
        $form['cambridge_mediamap'] = array(
          '#type' =&gt; 'fieldset',
          '#title' =&gt; t('Media Map'),
          '#collapsible' =&gt; TRUE,
          '#collapsed' =&gt; FALSE,
          '#tree' =&gt; TRUE,
        );
        //insert the embed code
        $form['cambridge_mediamap']['embed'] = array(
          '#type' =&gt; 'textarea',
          '#title' =&gt; t('Video Embed Code'),
          '#default_value' =&gt;  $form['#node']-&gt;cambridge_mediamap['embed'],
          '#cols' =&gt; 60,
          '#rows' =&gt; 5,
          '#description' =&gt; t('Copy and paste the embed code from an external video or media hosting service'),
        );
        //enable or disable on map
        $form['cambridge_mediamap']['display'] = array(
          '#type' =&gt; 'select',
          '#title' =&gt; t('Display this node'),
          '#default_value' =&gt; $form['#node']-&gt;cambridge_mediamap['display'],
          '#options' =&gt; array(
            '0' =&gt; t('Disable display'),
            '1' =&gt; t('Enable display'),
          ),
        );

      }
      break;
  }
}

/**
 * A page callback to draw the map
 */
function cambridge_mediamap_map() {
  $output = '';

  //Collect the nodes to be displayed
  $results = db_query('SELECT embed, nid FROM {cambridge_mediamap} WHERE display = 1');

  //Initialize our marker array
  $markers = array();

  //check to see what modules are enabled
  $location_enabled = module_exist('location');
  $gmap_location_enabled = module_exist('gmap_location');

  //load each node and set it's attributes in the marker array
  while($item = db_fetch_object($results)) {
    $latitude = 0;
    $longitude = 0;
    //load the node
    $node = node_load(array('nid' =&gt; $item-&gt;nid));

    //set the latitude and longitude
    //give location module data preference over gmap module data
    if ($location_enabled) {
      $latitude = $node-&gt;location['latitude'];
      $longitude = $node-&gt;location['longitude'];
    }
    elseif ($gmap_location_enabled) {
      $latitude = $node-&gt;gmap_location_latitude;
      $longitude = $node-&gt;gmap_location_longitude;
    }

    if ($latitude &amp;&amp; $longitude) {
      $markers[] = array(
        'label' =&gt; theme('cambridge_mediamap_marker', $node),
        'latitude' =&gt; $latitude,
        'longitude' =&gt; $longitude,
        'markername' =&gt; variable_get('cambridge_mediamap_default_marker', 'marker'),
      );
    }
  }

  $latlon = explode(',', variable_get('cambridge_mediamap_default_latlong','42.369452,-71.100426'));

  $map=array(
    'id' =&gt; 'cambridge_mediamap',
    'latitude' =&gt; trim($latlon[0]),
    'longitude'=&gt; trim($latlon[1]),
    'width' =&gt; variable_get('cambridge_mediamap_default_width','100%'),
    'height' =&gt; variable_get('cambridge_mediamap_default_height','500px'),
    'zoom' =&gt; variable_get('cambridge_mediamap_default_zoom', 13),
    'control' =&gt; variable_get('cambridge_mediamap_default_control','Large'),
    'type' =&gt; variable_get('cambridge_mediamap_default_type','Satellite'),
    'markers' =&gt; $markers,
    );

  return gmap_draw_map($map);
}

/**
 * A theme function for our markers
 */

function theme_cambridge_mediamap_marker($node) {

  $output = '
<div class="mediamap-marker">';
  $output .= '
<div class="title">' . l($node-&gt;title, 'node/' . $node-&gt;nid) . '</div>

';
  $output .= '
<div class="embed">' . $node-&gt;cambridge_mediamap['embed_resize'] . '</div>

';
  $output .= '</div>

';

  return $output;
}

/**
 * Settings page
 */
function cambridge_mediamap_settings() {
   // Cambridge data
   //  latitude = 42.369452
   //  longitude = -71.100426

  $form['defaults']=array(
    '#type' =&gt; 'fieldset',
    '#title' =&gt; t('Default map settings'),
  );

  $form['defaults']['cambridge_mediamap_default_width'] = array(
    '#type' =&gt; 'textfield',
    '#title' =&gt; t('Default width'),
    '#default_value' =&gt; variable_get('cambridge_mediamap_default_width','100%'),
    '#size' =&gt; 25,
    '#maxlength' =&gt; 6,
    '#description' =&gt; t('The default width of a Google map. Either px or %'),
  );
  $form['defaults']['cambridge_mediamap_default_height'] = array(
    '#type' =&gt; 'textfield',
    '#title' =&gt; t('Default height'),
    '#default_value' =&gt; variable_get('cambridge_mediamap_default_height','500px'),
    '#size' =&gt; 25,
    '#maxlength' =&gt; 6,
    '#description' =&gt; t('The default height of Mediamap. In px.'),
  );
  $form['defaults']['cambridge_mediamap_default_latlong'] = array(
    '#type' =&gt; 'textfield',
    '#title' =&gt; t('Default center'),
    '#default_value' =&gt; variable_get('cambridge_mediamap_default_latlong','42.369452,-71.100426'),
    '#description' =&gt; 'The decimal latitude,longitude of the centre of the map.  The "." is used for decimal, and "," is used to separate latitude and longitude.',
    '#size' =&gt; 50,
    '#maxlength' =&gt; 255,
    '#description' =&gt; t('The default longitude, latitude of Mediamap.'),
  );
  $form['defaults']['cambridge_mediamap_default_zoom']=array(
    '#type'=&gt;'select',
    '#title'=&gt;t('Default zoom'),
    '#default_value'=&gt;variable_get('cambridge_mediamap_default_zoom', 13),
    '#options' =&gt; drupal_map_assoc(range(0, 17)),
    '#description'=&gt;t('The default zoom level of Mediamap.'),
  );
  $form['defaults']['cambridge_mediamap_default_control']=array(
    '#type'=&gt;'select',
    '#title'=&gt;t('Default control type'),
    '#default_value'=&gt;variable_get('cambridge_mediamap_default_control','Large'),
    '#options'=&gt;array('None'=&gt;t('None'),'Small'=&gt;t('Small'),'Large'=&gt;t('Large')),
  );
  $form['defaults']['cambridge_mediamap_default_type']=array(
    '#type'=&gt;'select',
    '#title'=&gt;t('Default map type'),
    '#default_value'=&gt;variable_get('cambridge_mediamap_default_type','Satellite'),
    '#options'=&gt;array('Map'=&gt;t('Map'),'Satellite'=&gt;t('Satellite'),'Hybrid'=&gt;t('Hybrid')),
  );

  $markers = gmap_get_markers();

  $form['defaults']['cambridge_mediamap_default_marker'] = array(
    '#type'=&gt;'select',
    '#title'=&gt;t('Marker'),
    '#default_value'=&gt;variable_get('cambridge_mediamap_default_marker', 'marker'),
    '#options'=&gt;$markers,
  );

  $form['embed']=array(
    '#type' =&gt; 'fieldset',
    '#title' =&gt; t('Default embedded video settings'),
  );

  $form['embed']['cambridge_mediamap_embed_width'] = array(
    '#type' =&gt; 'textfield',
    '#title' =&gt; t('Default width'),
    '#default_value' =&gt; variable_get('cambridge_mediamap_embed_width','320'),
    '#size' =&gt; 25,
    '#maxlength' =&gt; 6,
    '#description' =&gt; t('The maximum width of embedded video'),
  );
  $form['embed']['cambridge_mediamap_embed_height'] = array(
    '#type' =&gt; 'textfield',
    '#title' =&gt; t('Default height'),
    '#default_value' =&gt; variable_get('cambridge_mediamap_embed_height','240'),
    '#size' =&gt; 25,
    '#maxlength' =&gt; 6,
    '#description' =&gt; t('The maximum height of embedded video.'),
  );

  return $form;
}

/**
 * Prints human-readable (html) information about a variable.
 * Use: print debug($variable_name);
 * Or assign output to a variable.
 */
function debug($value) {
 return preg_replace("/\s/", " ", preg_replace("/\n/", "",
print_r($value, true)));
}</code></pre>


<p><strong>Related posts:</strong><ol><li><a href='http://www.island94.org/2011/03/the-48-hour-mobile-web-app-drunken-stumble/' rel='bookmark' title='The 48 hour mobile web app: Drunken Stumble'>The 48 hour mobile web app: Drunken Stumble</a> <small>Last weekend I participated in the Boston Hack Day Challenge, a 48 hour (so I’m not sure why they called...</small></li>
<li><a href='http://www.island94.org/2011/03/a-form-from-my-favorites/' rel='bookmark' title='A form from my favorites'>A form from my favorites</a> <small>Above is the signup form from Brompt, the blog reminder service I built a few years ago for undisciplined bloggers...</small></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.island94.org/2007/08/geekout-video-on-maps-for-cable-access-tv/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>

<!-- Performance optimized by W3 Total Cache. Learn more: http://www.w3-edge.com/wordpress-plugins/

Minified using disk: basic
Page Caching using disk: enhanced

Served from: www.island94.org @ 2012-02-10 14:04:38 -->
