<?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>New Media Initiatives &#187; Web Development</title>
	<atom:link href="http://blogs.walkerart.org/newmedia/category/web-development/feed/" rel="self" type="application/rss+xml" />
	<link>http://blogs.walkerart.org/newmedia</link>
	<description>Just another Walker Blogs weblog</description>
	<lastBuildDate>Thu, 12 Nov 2009 20:27:13 +0000</lastBuildDate>
	<generator>walker_blogs</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Building the Walker&#8217;s mobile site, part 2 &#8212; google analytics without javascript</title>
		<link>http://blogs.walkerart.org/newmedia/2009/11/12/building-walkers-mobile-site-google-analytics-without-javascript-pt2/</link>
		<comments>http://blogs.walkerart.org/newmedia/2009/11/12/building-walkers-mobile-site-google-analytics-without-javascript-pt2/#comments</comments>
		<pubDate>Thu, 12 Nov 2009 20:27:13 +0000</pubDate>
		<dc:creator>Justin Heideman</dc:creator>
				<category><![CDATA[Mobile Devices]]></category>
		<category><![CDATA[Web Development]]></category>

		<guid isPermaLink="false">http://blogs.walkerart.org/newmedia/?p=991</guid>
		<description><![CDATA[As I mentioned in my last post on our mobile site, one of the key features for our site was making sure that we don&#8217;t use any javascript unless absolutely necessary. If you use Google Analytics  (GA) as your stats package, this poses a problem, since the supported way to run GA is via a [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://blogs.walkerart.org/newmedia/files/2009/11/ga_mobile.jpg"><img class="alignright size-medium wp-image-1030" src="http://blogs.walkerart.org/newmedia/files/2009/11/ga_mobile-450x438.jpg" alt="ga_mobile" width="315" height="307" /></a>As I mentioned in my last post on our mobile site, one of the key features for our site was making sure that we don&#8217;t use any javascript unless absolutely necessary. If you use Google Analytics  (GA) as your stats package, this poses a problem, since the supported way to run GA is via a chunk of javascript at the bottom of every page. And to make matters worse, the ga.js file is not gzipped, so you&#8217;re loading 9K which would otherwise be about 4k, on a platform where every byte counts. By contrast, if you could just serve the tracking gif, it is 47 bytes. And no javascript that might not run on B-grade or below devices.</p>
<p>A few weeks ago, Google announced <a href="http://analytics.blogspot.com/2009/10/google-analytics-now-more-powerful.html">support for analytics inside mobile apps and some cursory support for mobile sites</a>:</p>
<blockquote><p>Google Analytics now tracks mobile websites and mobile apps so you can better measure your mobile marketing efforts. If you&#8217;re optimizing content for mobile users and have created a mobile website, Google Analytics can track traffic to your mobile website from all web-enabled devices, whether or not the device runs JavaScript. This is made possible by adding a server side code snippet to your mobile website which will become available to all accounts in the coming weeks (<a href="http://www.google.com/analytics/googleanalyticsformobile.zip">download snippet instructions</a>). We will be supporting PHP, Perl, JSP and ASPX sites in this release. Of course, you can still track visits to your regular website coming from high-end, Javascript enabled phones.</p></blockquote>
<p>And that is the extent of the documentation you will find anywhere on Google on how to run analytics without javascript. The code included is handy if you happen to run one of their platforms, but the <a href="http://m.walkerart.org">Walker&#8217;s mobile site</a> runs on the python side of AppEngine, so their code doesn&#8217;t do us much good. Thankfully, since they provide us with the source, we can without too much trouble, translate the php or perl into python and make it AppEngine friendly.</p>
<h5>How it works</h5>
<p>Regular Google Analytics works by serving some javascript and a small 1px x 1px gif file to your site from Google. The gif lets Google learn many things from the HTTP request your browser makes, such as your browser, OS, where you came from, your rough geo location, etc. The javascript lets them learn all kinds of nifty things about your screen, flash versions, event that fire, etc. And Google tracks you through a site by setting some cookies on that gif they serve you.</p>
<p>To use GA without javascript, we can still do most of that, and we do it by generating our own gif file and passing some information back to Google through our server. That is, we generate a gif, assign and track our own cookie, and then gather that information as you move through the site, and use a HTTP request with the appropriate query strings and pass it back to Google, which they then compile and treat as regular old analytics.</p>
<h5>The Code</h5>
<p>To make this work in appeinge, we create a  URL in our webapp that we&#8217;ll serve the gif from. I&#8217;m using &#8220;/ga/&#8221;:</p>
<pre class="brush: python;">
def main():
application = webapp.WSGIApplication(
[('/', home.MainHandler),
# edited out extra lines here
('/ga/', ga.GaHandler),
],
debug=False)
wsgiref.handlers.CGIHandler().run(application)
</pre>
<p>And here&#8217;s the big handler for /ga/. I based it mostly off the php and some of the perl (click to expand the full code):</p>
<pre class="brush: python; collapse: true; light: false; toolbar: true;">
from google.appengine.ext import webapp
from google.appengine.api import urlfetch
import re, hashlib, random, time, datetime, cgi, urllib, uuid

# google analytics stuff
VERSION = &quot;4.4sh&quot;
COOKIE_NAME = &quot;__utmmobile&quot;

# The path the cookie will be available to, edit this to use a different cookie path.
COOKIE_PATH = &quot;/&quot;

# Two years in seconds.
COOKIE_USER_PERSISTENCE = 63072000

GIF_DATA = [
      chr(0x47), chr(0x49), chr(0x46), chr(0x38), chr(0x39), chr(0x61),
      chr(0x01), chr(0x00), chr(0x01), chr(0x00), chr(0x80), chr(0xff),
      chr(0x00), chr(0xff), chr(0xff), chr(0xff), chr(0x00), chr(0x00),
      chr(0x00), chr(0x2c), chr(0x00), chr(0x00), chr(0x00), chr(0x00),
      chr(0x01), chr(0x00), chr(0x01), chr(0x00), chr(0x00), chr(0x02),
      chr(0x02), chr(0x44), chr(0x01), chr(0x00), chr(0x3b)
  ]

class GaHandler(webapp.RequestHandler):
	def getIP(self,remoteAddress):
	  	if remoteAddress == '' or remoteAddress == None:
			return ''

		#Capture the first three octects of the IP address and replace the forth
		#with 0, e.g. 124.455.3.123 becomes 124.455.3.0
		res = re.findall(r'\d+\.\d+\.\d+\.', remoteAddress)
		if res:
			return res[0] + &quot;0&quot;
		else:
			return &quot;&quot;

	def getVisitorId(self, guid, account, userAgent, cookie):
		#If there is a value in the cookie, don't change it.
		if type(cookie).__name__ != 'NoneType': # or len(cookie)!=0:
			return cookie

		message = &quot;&quot;

		if type(guid).__name__ != 'NoneType': # or len(guid)!=0:
			#Create the visitor id using the guid.
			message = guid + account
		else:
			#otherwise this is a new user, create a new random id.
			message = userAgent + uuid.uuid1(self.getRandomNumber()).__str__()

		m = hashlib.md5()
		m.update(message)
		md5String = m.hexdigest()

		return str(&quot;0x&quot; + md5String[0:16])

	def getRandomNumber(self):
		return random.randrange(0, 0x7fffffff)

	def sendRequestToGoogleAnalytics(self,utmUrl):
		'''
		Make a tracking request to Google Analytics from this server.
		Copies the headers from the original request to the new one.
		If request containg utmdebug parameter, exceptions encountered
		communicating with Google Analytics are thown.
		'''
		headers = {
			&quot;user_agent&quot;: self.request.headers.get('user_agent'),
			&quot;Accepts-Language&quot;: self.request.headers.get('http_accept_language'),
			}
		if len(self.request.get(&quot;utmdebug&quot;))!=0:
			data = urlfetch.fetch(utmUrl, headers=headers)
		else:
			try:
				data = urlfetch.fetch(utmUrl, headers=headers)
			except:
				pass

	def get(self):
		'''
		Track a page view, updates all the cookies and campaign tracker,
		makes a server side request to Google Analytics and writes the transparent
		gif byte data to the response.
		'''
	  	timeStamp = time.time()

		domainName = self.request.headers.get('host')
		domainName = domainName.partition(':')[0]

		if len(domainName) == 0:
			domainName = &quot;m.walkerart.org&quot;;

		#Get the referrer from the utmr parameter, this is the referrer to the
		#page that contains the tracking pixel, not the referrer for tracking
		#pixel.
		documentReferer = self.request.get(&quot;utmr&quot;)

		if len(documentReferer) == 0 or documentReferer != &quot;0&quot;:
			documentReferer = &quot;-&quot;
		else:
			documentReferer = urllib.unquote_plus(documentReferer)

		documentPath = self.request.get(&quot;utmp&quot;)
		if len(documentPath)==0:
			documentPath = &quot;&quot;
		else:
			documentPath = urllib.unquote_plus(documentPath)

		account = self.request.get(&quot;utmac&quot;)
		userAgent = self.request.headers.get(&quot;user_agent&quot;)
		if len(userAgent)==0:
			userAgent = &quot;&quot;

		#Try and get visitor cookie from the request.
		cookie = self.request.cookies.get(COOKIE_NAME)

		visitorId = str(self.getVisitorId(self.request.headers.get(&quot;HTTP_X_DCMGUID&quot;), account, userAgent, cookie))

		#Always try and add the cookie to the response.
		d = datetime.datetime.fromtimestamp(timeStamp + COOKIE_USER_PERSISTENCE)
		expireDate = d.strftime('%a,%d-%b-%Y %H:%M:%S GMT')

		self.response.headers.add_header('Set-Cookie', COOKIE_NAME+'='+visitorId +'; path='+COOKIE_PATH+'; expires='+expireDate+';' )
	  	utmGifLocation = &quot;http://www.google-analytics.com/__utm.gif&quot;

		myIP = self.getIP(self.request.remote_addr)

		#Construct the gif hit url.
		utmUrl = utmGifLocation + &quot;?&quot; + &quot;utmwv=&quot; + VERSION  + \
			&quot;&amp;utmn=&quot; + str(self.getRandomNumber()) + \
			&quot;&amp;utmhn=&quot; + urllib.pathname2url(domainName) + \
			&quot;&amp;utmr=&quot; + urllib.pathname2url(documentReferer) + \
			&quot;&amp;utmp=&quot; + urllib.pathname2url(documentPath) + \
			&quot;&amp;utmac=&quot; + account + \
			&quot;&amp;utmcc=__utma%3D999.999.999.999.999.1%3B&quot; + \
			&quot;&amp;utmvid=&quot; + str(visitorId) + \
			&quot;&amp;utmip=&quot; + str(myIP)

		# we dont send requests when we're developing
		if domainName != 'localhost':
			self.sendRequestToGoogleAnalytics(utmUrl)

		#If the debug parameter is on, add a header to the response that contains
		#the url that was used to contact Google Analytics.
		if len(self.request.get(&quot;utmdebug&quot;)) != 0:
			self.response.headers.add_header(&quot;X-GA-MOBILE-URL&quot; , utmUrl)

	  	#Finally write the gif data to the response.
		self.response.headers.add_header('Content-Type', 'image/gif' )
		self.response.headers.add_header('Cache-Control', 'private, no-cache, no-cache=Set-Cookie, proxy-revalidate' )
		self.response.headers.add_header('Pragma', 'no-cache' )
		self.response.headers.add_header('Expires', 'Wed, 17 Sep 1975 21:32:10 GMT' )
		self.response.out.write(''.join(GIF_DATA))
</pre>
<p>So now we know what to do with our requests at /ga/ when we get them, we just need to make the proper requests to that URL in the first place. So we need to generate the URL we&#8217;re going to have the visitor&#8217;s browser request in the first place. With normal django, we would be able to use template_context to automatically insert it into the page&#8217;s template values. But, since AppEngine doesn&#8217;t use that, we have our own helper functions to do that, which I showed some of in my last post. Here&#8217;s the updated helper functions, with the GoogleAnalyticsGetImageUrl function included:</p>
<pre class="brush: python;">
import settings

def googleAnalyticsGetImageUrl(request):
	url = &quot;&quot;
	url += '/ga/' + &quot;?&quot;
	url += &quot;utmac=&quot; + settings.GA_ACCOUNT
	url += &quot;&amp;utmn=&quot; + str(random.randrange(0, 0x7fffffff))

	referer = request.referrer
	query = urllib.urlencode(request.GET) #$_SERVER[&quot;QUERY_STRING&quot;];
	path = request.path #$_SERVER[&quot;REQUEST_URI&quot;];

	if len(referer) == 0:
		referer = &quot;-&quot;

	url += &quot;&amp;utmr=&quot; + urllib.pathname2url(referer)

	if len(path)!=0:
		url += &quot;&amp;utmp=&quot; + urllib.pathname2url(path)

	url += &quot;&amp;guid=ON&quot;;

	return {'gaImgUrl':url}

def getTempalteValues(request):
	myDict = {}
	myDict.update(ua_test(request))
	myDict.update(googleAnalyticsGetImageUrl(request))
	return myDict
</pre>
<p>Assuming we use getTemplateValues to set up our inital template_values dict, we should have a variable named &#8216;gaImgUrl&#8217; in our page. To use it, all we need to do is put this at the bottom of every page on the site:</p>
<pre class="brush: xml;">
&lt;img src=&quot;{{ gaImgUrl }}&quot; alt=&quot;analytics&quot; /&gt;
</pre>
<p>My <code>settings</code> file contains  the GA_ACCOUNT variable, but replaces the standard GA-XXXXXX-X setup with MO-XXXXXX-X. I&#8217;m assuming the MO- tells google that it&#8217;s a mobile so accept the proxied requests.</p>
<p>One thing to keep in mind with this technique is that you cannot cache your rendered templates. The image you server will necessarily have a different query string every time, and if you cached it, you would ruin your analytics. Instead, you should cache nearly everything from your view functions, except the gaImgUrl variable.</p>
]]></content:encoded>
			<wfw:commentRss>http://blogs.walkerart.org/newmedia/2009/11/12/building-walkers-mobile-site-google-analytics-without-javascript-pt2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Building the Walker&#8217;s mobile website with Google AppEngine, part 1</title>
		<link>http://blogs.walkerart.org/newmedia/2009/11/09/building-the-walkers-mobile-website-with-google-appengine-part-1/</link>
		<comments>http://blogs.walkerart.org/newmedia/2009/11/09/building-the-walkers-mobile-website-with-google-appengine-part-1/#comments</comments>
		<pubDate>Mon, 09 Nov 2009 23:26:18 +0000</pubDate>
		<dc:creator>Justin Heideman</dc:creator>
				<category><![CDATA[Mobile Devices]]></category>
		<category><![CDATA[Web Development]]></category>

		<guid isPermaLink="false">http://blogs.walkerart.org/newmedia/?p=956</guid>
		<description><![CDATA[Over the summer, our department made a small but significant policy change. We decided to take a cue from Google&#8217;s 20% time philosophy and spend one day a week working on a Walker-related project of our choosing. Essentially, we wanted to embark on quicker, more nimble projects that hold more interest for our team. The [...]]]></description>
			<content:encoded><![CDATA[<p><img class="alignright size-medium wp-image-963" src="http://blogs.walkerart.org/newmedia/files/2009/11/mwalker-iphone-234x450.png" alt="mwalker-iphone" width="234" height="450" />Over the summer, our department made a small but significant policy change. We decided to take a cue from Google&#8217;s <a href="http://googleblog.blogspot.com/2006/05/googles-20-percent-time-in-action.html">20% time philosophy</a> and spend one day a week working on a Walker-related project of our choosing. Essentially, we wanted to embark on quicker, more nimble projects that hold more interest for our team. The project I decided to experiment with was making a <a href="http://m.walkerart.org">mobile website for the Walker, m.walkerart.org</a>.</p>
<h5>Reviewing our current site to inform the mobile site</h5>
<p>The web framework we use for most of our site has the ability, with some small changes, to load different versions of a page based on a visitor&#8217;s User Agent (what browser they&#8217;re using). This would mean we could detect if a visitor was running IE on a Desktop or Mobile Safari on an iPhone, and serve each of them two different versions of a page. This is how a lot of mobile sites are done.</p>
<p>This is not the approach we went with for our mobile site, because it violates two of the primary rules (in my mind) of making a mobile website:</p>
<ol>
<li>Make it simple.</li>
<li>Give people the stuff they&#8217;re looking for on their phones right away.</li>
</ol>
<p>Our site is complicated: we have pages for different disciplines, a calendar with years of archives, and many specialty sites. Rule #1, violated. To address #2, I took a look at our web analytics to figure out what most people come to our site looking for. This won&#8217;t surprise anyone, but it&#8217;s hours, admission, directions, and what&#8217;s happening today at the Walker:</p>
<div id="attachment_960" class="wp-caption alignnone" style="width: 344px"><img class="size-full wp-image-960" src="http://blogs.walkerart.org/newmedia/files/2009/11/topContent.jpg" alt="Top Walker Pages as noted by Google Analytics" width="334" height="296" /><p class="wp-caption-text">Top Walker Pages as noted by Google Analytics</p></div>
<p>So it seems pretty clear those things should be up front. One of the other things you might want to access on a mobile is <a href="http://newmedia.walkerart.org/aoc">Art on Call</a>. While Art on Call is designed primarily around dial-in access, there is also a website, but it isn&#8217;t optimized for the small screen of a smartphone. We have WiFi in most spaces within our building, so accessing Art on Call via an web interface and streaming audio via HTTP rather than POTS is a distinct possibility that I wanted to enable.</p>
<h5>Prototyping with Google AppEngine</h5>
<p>I decided to develop a quick prototype using <a href="http://code.google.com/appengine/">Google AppEngine</a>, thinking I&#8217;d end up using GAE in the end, too. Because this was a 20% time project, I had the freedom to do it using the technology I was interested in. AppEngine has the advantage of being something that isn&#8217;t hosted on our server, so there was no need to configure any complicated server stuff. In my mind, AppEngine is perfect for a mobile site because of the low bandwidth requirements for a mobile site. Google doesn&#8217;t provide a ton for free, but if your pages are only 20K each, you can fit quite a bit within the quotas they do give provide. AppEngine&#8217;s primary language is also python, a big plus, since python is the best programming language.</p>
<p>In about two days I built a proof of concept mobile site that displayed the big-ticket pages (hours, admission,etc.) and had a simple interface for Art on Call. Using <a href="http://code.google.com/p/iui/">iUi</a> as a front-end framework was really, really useful here, because it meant that the amount of HTML/CSS/JS I had to code was super minimal, and I didn&#8217;t have to design anything.</p>
<p>I showed the prototype to <a href="http://blogs.walkerart.org/newmedia/author/robin/">Robin</a> and she enthusiastically gave me the green light to work on it full-time.</p>
<h5>Designing a mobile website</h5>
<p>A strategy I saw when looking at mobile sites was to actually have <em>two</em> mobile sites: one for the A-grade phones (iPhone, Nokia S60, Android, Pre) and one for the B- and C-grade phones (Blackberry and Windows Mobile). The main difference between the two is the use of javascript and some more advanced layout. Depending on what version of Blackberry you look at, they have a pretty lousy HTML/CSS implementation, and horrendous or no javascript support.</p>
<p>To work around this, our mobile site does not use any javascript on most pages and tries to keep the HTML/CSS pretty simple. We don&#8217;t do any fancy animations to load between pages like iUi or <a href="http://www.jqtouch.com/">jQtouch</a> do: even on an iPhone those animations are slow. If you make your pages small enough, they should load fast enough and a transition is not necessary.</p>
<p>Designing mobile pages is fun. The size and interface methods for the device force you to re-think how to people interact and what&#8217;s important. They&#8217;re also fun because they&#8217;re blissfully simple. Each page on our mobile site is usually just a headline, image, paragraph or two, and some links. Laying out and styling that content is not rocket surgery.</p>
<p>Initially, when I did my design mockups in Photoshop, I wanted to use a numpad on the site for <a href="http://newmedia.walkerart.org/aoc/">Art on Call</a>, much like the iPhone features for making a phone call. There&#8217;s no easy input for doing this, but I thought it wouldn&#8217;t be too hard to create one with a little javascript (for those that had it). Unfortunately, due to the way touchscreen phones handle click/touch events in the browser, there&#8217;s a delay between when you touch and when the click event fires in javascript. This meant that it was possible to touch/type the number much faster than the javascript events fired. No go.</p>
<p>Instead, the latest versions of WebKit provide with a HTML5 <a href="http://www.bennadel.com/blog/1721-Default-To-The-Numeric-Email-And-URL-Keyboards-On-The-iPhone.htm">input field with a type of &#8220;number&#8221;</a>. On iPhone OS 3.1 and better, it will bring up the keypad already switched to the numeric keys. It does not do this on iPhone OS prior to 3.1. I&#8217;m not sure how Android and Pre handle it.</p>
<div id="attachment_968" class="wp-caption alignleft" style="width: 254px"><img class="size-medium wp-image-968 " src="http://blogs.walkerart.org/newmedia/files/2009/11/aoc_mockup-244x450.jpg" alt="Mocked up Art on Call code input." width="244" height="450" /><p class="wp-caption-text">Mocked up Art on Call code input.</p></div>
<div id="attachment_967" class="wp-caption alignleft" style="width: 250px"><img class="size-medium wp-image-967  " src="http://blogs.walkerart.org/newmedia/files/2009/11/aoc_input_actual-300x450.jpg" alt="Implimented Art on Call code input." width="240" height="360" /><p class="wp-caption-text">Implimented Art on Call code input.</p></div>
<p><br class="clear" /></p>
<h5>Comparing smartphones</h5>
<p>Here&#8217;s a few screenshots of the site on various phones:</p>
<div id="attachment_976" class="wp-caption alignleft" style="width: 250px"><img class="size-medium wp-image-976 " src="http://blogs.walkerart.org/newmedia/files/2009/11/pre-300x450.jpg" alt="Palm Pre" width="240" height="360" /><p class="wp-caption-text">Palm Pre</p></div>
<div id="attachment_977" class="wp-caption alignleft" style="width: 250px"><img class="size-medium wp-image-977 " src="http://blogs.walkerart.org/newmedia/files/2009/11/android-300x450.jpg" alt="Android 1.5" width="240" height="360" /><p class="wp-caption-text">Android 1.5</p></div>
<div id="attachment_978" class="wp-caption alignleft" style="width: 298px"><img class="size-medium wp-image-978  " src="http://blogs.walkerart.org/newmedia/files/2009/11/bb9630-450x338.jpg" alt="Blackberry 9630" width="288" height="216" /><p class="wp-caption-text">Blackberry 9630</p></div>
<p><br class="clear" /><br />
Not pictured is Windows Mobile, because it looks really bad.</p>
<p>A future post may cover getting all of these emulators up and running, because it&#8217;s not as straight easy as it should be. Working with the blackberry emulator is especially painful.<br />
<br class="clear" /></p>
<h5>How our mobile site works</h5>
<p>The basic methodology for our mobile site is to pull the data, via either RSS or XML from our main website, parse it, cache it, and re-template it for mobile visitors. Nearly all of the pages on our site are available via XML if you know how to look. Parsing XML into usable data is a computationally expensive task, so caching is very important. Thankfully, AppEngine provides easy access to memcache, so we can memcache the XML fetches and the parsing as much as possible. Here&#8217;s our simple but effective URL parse/cache helper function:</p>
<pre class="brush: python;">
from google.appengine.api import urlfetch
from xml.dom import minidom
from google.appengine.api import memcache

def parse(url,timeout=3600):
	memKey = hash(url)
	r = memcache.get('fetch_%s' % memKey)
	if r == None:
		r = urlfetch.fetch(url)
		memcache.add(key=&quot;fetch_%s&quot; % memKey, value=r, time=timeout)
	if r.status_code == 200:
		dom = memcache.get('dom_%s' % memKey)
		if dom == None:
			dom = minidom.parseString(r.content)
			memcache.add(key=&quot;dom_%s&quot; % memKey, value=dom, time=timeout)
		return dom
	else:
		return False
</pre>
<p>Google AppEngine does not impose much of a structure for your web app. Similar to <a href="http://docs.djangoproject.com/en/dev/topics/http/urls/">Django&#8217;s urls.py</a>, you link regular expressions for URLS to class-based handlers. You can&#8217;t pass any variables beyond what&#8217;s in the URL or the <a href="http://pythonpaste.org/webob/">WebOb</a> to the request handler. Each handler will call a different method, depending if it&#8217;s a GET, POST, DELETE, http request. If you&#8217;re coming from the django world like me, this is not much of a big deal at first, but it gets tedious pretty fast. If I had it to do over again, I&#8217;d probably use <a href="http://code.google.com/p/app-engine-patch/">app-engine-patch</a> from the outset, and thus be able to use all the normal django goodies like middleware, template context, and way more configurable urls.</p>
<p>Within each handler, we also cache the generated data where possible. That is, after our get handler has run, we cache all the values that we pass to our template that won&#8217;t change over time. Here&#8217;s an example of the classes that handle the visit section of our mobile site:</p>
<pre class="brush: python;">
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
from google.appengine.api import memcache
from xml.dom import minidom
from google.appengine.api import memcache
from utils import feeds, parse, template_context, text
import settings

class VisitDetailHandler(webapp.RequestHandler):
	def get(self):
		url = self.request.get(&quot;s&quot;) + &quot;?style=xml&quot;
		template_values = template_context.getTempalteValues(self.request)
		path = settings.TEMPLATE_DIR + 'info.html'
		memKey = hash(url)

		r = memcache.get('visit_%s' % memKey)
		if r and not settings.DEBUG:
			template_values.update(r)
			self.response.out.write(template.render(path, template_values))
		else:
			dom = parse.parse(url)
			records = dom.getElementsByTagName(&quot;record&quot;)
			contents = []
			for rec in records:
				title = text.clean_utf8(rec.getElementsByTagName('title')[0].childNodes[0].nodeValue)
				body = text.clean_utf8(rec.getElementsByTagName('body')[0].childNodes[0].nodeValue)
				contents.append({'title':title,'body':body})

			back = {'href':'/visit/#top', 'text':'Visiting'}
			cacheableTemplateValues = { &quot;contents&quot;: contents,'back':back }
			memcache.add(key='visit_%s' % memKey, value={ &quot;contents&quot;: contents,'back':back }, time=7200)
			template_values.update(cacheableTemplateValues)
			self.response.out.write(template.render(path, template_values))
</pre>
<p>Dealing with parsing XML via the standard DOM methods is a great way to test your tolerance for pain. I would use libxml and xpath, AppEngine doesn&#8217;t provide those libraries in their python environment.</p>
<p>Because the only part of Django&#8217;s template system that AppEngine uses is the template language, and nothing else, we have to roll our own helper functions for context. Meaning, if we want to pass a bunch variables by default to our templates, something easy in django, we have to do it a little differently with GAE. I set up a function called getTemplateValues, which we pass the WebOb request, and it ferrets out and organizes info we need for the templates, passing it back as a dict.</p>
<pre class="brush: python;">
def ua_test(request):
	uastring = request.headers.get('user_agent')
	uaDict = {}
	if &quot;Mobile&quot; in uastring and &quot;Safari&quot; in uastring:
		uaDict['isIphone'] = True
	if 'BlackBerry' in uastring:
		uaDict['isBlackBerry'] = True
	return uaDict

def getTempalteValues(request):
	myDict = {}
	myDict.update(ua_test(request))
	myDict.update(googleAnalyticsGetImageUrl(request))
	return myDict
</pre>
<p>In my next post, I&#8217;ll talk about how to track visitors on a mobile site using google analytics, without using javascript.</p>
]]></content:encoded>
			<wfw:commentRss>http://blogs.walkerart.org/newmedia/2009/11/09/building-the-walkers-mobile-website-with-google-appengine-part-1/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>Behind-the-scenes of ArtsConnectEd: Art Finder</title>
		<link>http://blogs.walkerart.org/newmedia/2009/09/22/behind-the-scenes-of-artsconnected-art-finder/</link>
		<comments>http://blogs.walkerart.org/newmedia/2009/09/22/behind-the-scenes-of-artsconnected-art-finder/#comments</comments>
		<pubDate>Tue, 22 Sep 2009 20:21:00 +0000</pubDate>
		<dc:creator>Nate Solas</dc:creator>
				<category><![CDATA[ArtsConnectEd]]></category>
		<category><![CDATA[Web Development]]></category>

		<guid isPermaLink="false">http://blogs.walkerart.org/newmedia/?p=889</guid>
		<description><![CDATA[On September 1, 2009 the new ArtsConnectEd became available at ArtsConnectEd.org.  The new site provides access to more than 100,000 museum resources, including audio, video, images, and information about works of art, all of which can be saved and presented with the more powerful Art Collector.
This project was at least three years in the making, [...]]]></description>
			<content:encoded><![CDATA[<p>On September 1, 2009 the new ArtsConnectEd became available at <a href="http://www.artsconnected.org/">ArtsConnectEd.org</a>.  The new site provides access to more than 100,000 museum resources, including audio, video, images, and information about works of art, all of which can be saved and presented with the more powerful Art Collector.</p>
<p>This project was at least three years in the making, with the last two of those being the technical work of research, design, and development.  In this series of posts I&#8217;d like to present some of the decisions we struggled with and the process we went through in developing the new site.  I&#8217;ll start with the Art Finder, followed by a post on the Art Collector and presentations, and finish with a post about some of the more technical aspects including the data and harvesting technologies we&#8217;re using.</p>
<h1>Art Finder</h1>
<p>The Art Finder is the guts of the site, a portal into our thousands and thousands of objects, text records, and more.  I don&#8217;t think it&#8217;s an exaggeration to say designing and building this component was the biggest challenge we faced in the entire process.  We&#8217;ve redesigned the interface many times, often significantly, and are still not certain it&#8217;s right.  We&#8217;ve changed the underlying technology from a SQL / Lucene hybrid to a straight-up <a href="http://lucene.apache.org/solr/">Solr</a> search engine.  We&#8217;ve debated (endlessly) what fields to include, and what subset of our data to present in those fields.  We&#8217;ve gone back and forth over tab titles, and even whether to use tabs.  A rocky road, to say the least.</p>
<h2>The big idea</h2>
<p>What if we could start with <em>everything</em> and narrow it down from there?  Offer the user the entire collection and let them whittle away at it until they found what they wanted?</p>
<p><em><strong>It&#8217;s all browse.  Keyword is just another filter.</strong></em></p>
<p>To me this is the big breakthrough of the ArtsConnectEd interface.  We don&#8217;t hide the content behind a search box, or only show filters after you try a keyword.  We don&#8217;t have a separate page for &#8220;Advanced Search&#8221;, but we offer the same power through filters.  There is still a keyword field for those who know exactly what they&#8217;re looking for, but we get to use our metadata in a more powerful way than simple text.  That is, since know the difference between the <a href="http://www.artsconnected.org/resource/list/breadcrumb/true/category/work/f_workHasThumbnailMedia/on/query/painting/sortby/relevance/order/desc"><em>word</em> &#8220;painting&#8221;</a> appearing in the description and something that <a href="http://www.artsconnected.org/resource/list/breadcrumb/true/category/work/f_workHasThumbnailMedia/on/f_workDisplayResourceType/Paintings/sortby/title/order/asc"><em>is</em> a painting</a>, we can present that to the user through filters.</p>
<h2>How we Got here</h2>
<p><a href="http://blogs.walkerart.org/newmedia/files/2009/09/browse_wireframe.gif"><img class="size-thumbnail wp-image-896 alignleft" style="border: 2px solid black;margin-left: 5px;margin-right: 5px" src="http://blogs.walkerart.org/newmedia/files/2009/09/browse_wireframe-150x150.gif" alt="browse_wireframe" width="150" height="150" /></a>We wanted many ways for the user to explore the collection, with the idea we might hopefully mimic some of the serendipity of exploring a gallery.  The tech committee felt early on that we&#8217;d need, in addition to a robust search, some way to freely browse.  Our initial attempt was to split the Art Finder into a Browse interface (left) and a Search interface (right).<a href="http://blogs.walkerart.org/newmedia/files/2009/09/search_wireframe.gif"><img class="size-thumbnail wp-image-897 alignright" style="border: 2px solid black;margin-left: 5px;margin-right: 5px" src="http://blogs.walkerart.org/newmedia/files/2009/09/search_wireframe-150x150.gif" alt="search_wireframe" width="150" height="150" /></a></p>
<p>After forcing users to choose a content type to browse (Object, Text, etc), we exposed facets (fields) to allow filtering, e.g. by Medium or Style.  These facets were hidden by default in the Search interface, where instead you started with a keyword and content type as tabs &#8212; but could then click to reveal the same browse filters!  The more we played with these two ideas, the more we realized they were essentially the same thing, the only difference being a confusing first step and then having to learn two interfaces.  The real power of the site was in combining them, committing fully to Browse, and adding the keyword search as a filter.</p>
<p>Lastly, as we harvested more of our collections we realized pushing filters to the front offered a better way to drill down when many of our records are not text-heavy and thus less findable via keyword search.  In many ways browse leveled the playing field of our objects between those with healthy wall labels and those with more sparse metadata.</p>
<p style="text-align: center"><img class="size-full wp-image-910 aligncenter" style="border: 1px solid black" src="http://blogs.walkerart.org/newmedia/files/2009/09/fact_discovery.png" alt="fact_discovery" width="597" height="234" /></p>
<h2>What works</h2>
<p>(In my humble opinion!)  A good browse has to do a few things:</p>
<ul>
<li><strong>Be fast.</strong> Studies have shown that slow search (or browse) results derail a user&#8217;s chain of thought and makes it difficult to complete tasks.  We went one step further and did away with the &#8220;Go&#8221; button for everything but keyword &#8211; making a change to a pulldown automatically updates your result set.  (It&#8217;s not instant, but it&#8217;s fast enough the action feels connected to the results)</li>
<li><strong>Reduce complex fields to an intuitive subset.</strong> We have a huge range of unique strings for the Medium field, but we&#8217;ve broadly grouped them to present a reasonable-sized pulldown.  Likewise for the Culture pulldown.  (We manually reduce the terms for Medium, and have a automated Bayesian filter for the Culture field)</li>
<li><strong>Have good breadcrumbs.</strong> Users need to know what options are in effect and be able to backtrack easily.</li>
<li><strong>Avoid dead ends. </strong> With many interfaces it&#8217;s entirely too easy to browse yourself into an empty set.  By showing numbers next to our filter choices, we can help users avoid these &#8220;dead ends&#8221;.</li>
<li><strong>Expose variety.</strong> Type &#8220;Jasper Johns&#8221; in the artist field, and <a href="http://www.artsconnected.org/resource/list/breadcrumb/true/category/work/f_workHasThumbnailMedia/on/f_workDisplayCreator/Jasper+Johns/sortby/title/order/asc">check out</a> the Medium pulldown: it shows the bulk of his work is in Prints, but we also have a few sculptures, some mixed media, etc.  A nice way to see the variety of an artist&#8217;s work at-a-glance.</li>
<li><strong>Autocomplete complicated fields.</strong> If a search box is targeted to a field (like our Artist box), it needs to autocomplete.  Leaving a field like this open to free text is asking for frustration as people get 0 results for &#8220;<a href="http://www.artsconnected.org/resource/list/breadcrumb/true/category/work/f_workHasThumbnailMedia/on/query/Claes+Oldenberg/sortby/relevance/order/desc">Claes Oldenberg</a>&#8220;. (Auto-suggest &#8220;did you mean&#8221; should also work!)</li>
<li><strong>Have lots of sort options.</strong> One of my favorite features of the new Art Finder is the ability to <a href="http://www.artsconnected.org/resource/list/breadcrumb/true/category/work/f_workHasThumbnailMedia/on/f_workDisplayResourceType/Sculpture/sortby/size/order/desc">sort by size</a>.  Super cool.  (check out the Scale tab in the detail view for <a href="http://www.artsconnected.org/resource/86810/38/unpainted-sculpture/tab/scale#scale">more</a> <a href="http://www.artsconnected.org/resource/91421/56/the-parachutist/tab/scale#scale">fun</a>!)</li>
</ul>
<p>I&#8217;m biased after this project, but I&#8217;m fairly convinced combining faceted browsing with keyword search is absolutely the way to go for collection search.  It gives the best of both worlds, powerful but still intuitive.</p>
<p style="text-align: center"><img class="size-full wp-image-906 aligncenter" src="http://blogs.walkerart.org/newmedia/files/2009/09/facets_1.png" alt="facets_1" width="572" height="360" /></p>
<h2>What could be better</h2>
<p>&#8230; but is it really intuitive?  People seem to still be looking for a big inviting search box to start with.  The interface is crowded, and the number of options looks intimidating.  We&#8217;ve ended up avoiding using the words &#8220;Search&#8221; and &#8220;Browse&#8221; because they were loaded and causing confusion.  We&#8217;ve tried many versions of the tab bar to try to clarify what filters apply globally (e.g. Institution) and which only effect that tab (Works of Art have an Artist, for instance), but I don&#8217;t believe we&#8217;ve solved it.</p>
<p>I think the two components of the interface that give us the most trouble and confusion are actually the &#8220;Has Image&#8221; checkbox and the &#8220;Reset All&#8221; button.  These are consistently missed by people in testing, and we have tried almost everything we can think of.  Oh, and the back button.  The back button is &#8220;broken&#8221; in dynamic search like this.</p>
<p>Also, while I really like the look of the tiles in the results panel, we&#8217;ve had to heavily overload the rollover data to show fields we can sort by since there&#8217;s no more room in the tiles.  We also intended to create alternative result formats, such as text bars, etc, which could show highlights on matching keywords, but this item was pushed back for other features.</p>
<p>We&#8217;ve defaulted to sorting alphabetically by title when a user first reaches the page, and I&#8217;m no longer sure this is best.  As we&#8217;ve populated the collections in ArtsConnectEd we&#8217;ve ended up with a bunch of works that have numbers for titles, make the alpha sort less obvious.</p>
<p>You tell me!  Give <a href="http://www.artsconnected.org/">the site</a> a spin and post a comment &#8211; what works, and what could be better?</p>
<p style="padding-left: 30px"><span style="font-size:.75em">Resources:</span></p>
<ul>
<li><a href="http://www.uie.com/articles/faceted_search/">Designing for Faceted Search</a> (http://www.uie.com/articles/faceted_search/)</li>
<li><a href="http://www.uie.com/events/virtual_seminars/facets/FacetedSearchVS35Handout.pdf">Faceted Search: Designing Your Content, Navigation, and User Interface</a> (http://www.uie.com/events/virtual_seminars/facets/FacetedSearchVS35Handout.pdf)</li>
<li><a href="http://en.wikipedia.org/wiki/Faceted_search">Faceted Search</a> (http://en.wikipedia.org/wiki/Faceted_search)</li>
<li><a href="http://www.uxmatters.com/mt/archives/2009/09/best-practices-for-designing-faceted-search-filters.php">Best Practices for Designing Faceted Search Filters</a> (http://www.uxmatters.com/mt/archives/2009/09/best-practices-for-designing-faceted-search-filters.php)</li>
<li><a href="http://www.vam.ac.uk/cis-online/search/?q=blue&amp;commit=Search&amp;category%5B%5D=5&amp;narrow=1&amp;offset=0&amp;slug=0">V&amp;A Collections</a> (beta) (http://www.vam.ac.uk/cis-online/search/?q=blue&amp;commit=Search&amp;category%5B%5D=5&amp;narrow=1&amp;offset=0&amp;slug=0)
<ul>
<li>Their facets aren&#8217;t as up front as I&#8217;d like (you have to start with a keyword), but they&#8217;re done really well once they show up.</li>
<li>You can also cheat and leave keyword blank to get a full browse and go right to the facets&#8230;  Maybe start here?</li>
</ul>
</li>
<li><a href="http://www.moma.org/collection/search.php">MOMA Collections</a> (http://www.moma.org/collection/search.php)
<ul>
<li>Nice presentation of facets, but I wish two things: show me a number next to all constraints, not just artists, and let me add a keyword.  (I got a dead end looking for on-view film from the 20s or 2000s)  I also like that it&#8217;s a true browse &#8211; leaving everything at &#8220;All&#8221; seems to give me the whole collection.</li>
</ul>
</li>
</ul>
<p style="padding-left: 30px">
<p style="padding-left: 30px">
]]></content:encoded>
			<wfw:commentRss>http://blogs.walkerart.org/newmedia/2009/09/22/behind-the-scenes-of-artsconnected-art-finder/feed/</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
		<item>
		<title>Web and video standards roundup</title>
		<link>http://blogs.walkerart.org/newmedia/2009/09/17/web-and-video-standards-roundup/</link>
		<comments>http://blogs.walkerart.org/newmedia/2009/09/17/web-and-video-standards-roundup/#comments</comments>
		<pubDate>Thu, 17 Sep 2009 19:47:32 +0000</pubDate>
		<dc:creator>Justin Heideman</dc:creator>
				<category><![CDATA[Web Development]]></category>

		<guid isPermaLink="false">http://blogs.walkerart.org/newmedia/?p=886</guid>
		<description><![CDATA[   

Eric at Adapted Studio put together this sweet little demo of HTML5 and Canvas in action, in the form of the Game of life. Source code is included, too, if you want to learn a few nifty things.
Color me surprised, but Microsoft is actually purporting to work together on at least some [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://adaptedstudio.com/weblog/2009/sep/14/life-fun/"><img class="alignnone size-thumbnail wp-image-11" src="http://blogs.walkerart.org/files/2009/09/life2-150x150.gif" alt="life2" width="120" height="120" /></a> <a href="http://arstechnica.com/microsoft/news/2009/09/ie-program-manager-endorses-html-5-multimedia-tags.ars"><img class="alignnone size-thumbnail wp-image-10" src="http://blogs.walkerart.org/files/2009/09/513636061_98d07f7966-150x150.jpg" alt="html5 " width="120" height="120" /></a> <a href="http://ejohn.org/apps/learn/"><img class="alignnone size-thumbnail wp-image-12" src="http://blogs.walkerart.org/files/2009/09/Screen-shot-2009-09-17-at-2.35.28-PM-150x150.png" alt="learning advanced javascript" width="120" height="120" /></a> <a href="http://diveintomark.org/archives/2009/01/08/give-slides"><img class="alignnone size-thumbnail wp-image-13" src="http://blogs.walkerart.org/files/2009/09/Screen-shot-2009-09-17-at-2.36.06-PM-150x150.png" alt="audio codec wtf" width="120" height="120" /></a></p>
<ul>
<li>Eric at Adapted Studio put together this sweet little <a href="http://adaptedstudio.com/weblog/2009/sep/14/life-fun/">demo of HTML5 and Canvas in action</a>, in the form of the Game of life. Source code is included, too, if you want to learn a few nifty things.</li>
<li>Color me surprised, but Microsoft is actually purporting to <a href="http://arstechnica.com/microsoft/news/2009/09/ie-program-manager-endorses-html-5-multimedia-tags.ars">work together on at least some of the HTML5</a> spec. This could be good. Using &lt;video&gt; would be much easier if everyone would do it. But there still is the nasty issue of codecs, which is even more thorny than W3C specs.</li>
<li>This is from about a year ago, but John Resig (of jQuery fame) posted a very nice tutorial for <a href="http://ejohn.org/apps/learn/">Learning Advanced Javascript</a>. It clears up a lot of confusion about seemingly advanced techniques.</li>
<li>Also worth perusing is Mark Pilgrim&#8217;s <a href="http://diveintomark.org/archives/2008/12/18/give-part-1-container-formats">Gentle introduction to video formatting</a>. If you&#8217;re a video geek, you might know some of this, but there&#8217;s detail that might fill in some gaps. The <a href="http://diveintomark.org/archives/2009/01/08/give-slides">slides</a> are also slightly amusing. I had no idea the .mkv format came from a bunch of guys in Russia that decided to opensource it.</li>
</ul>
<p>HTML 5 image form <a href="http://www.flickr.com/photos/justinsomnia/513636061/">here</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://blogs.walkerart.org/newmedia/2009/09/17/web-and-video-standards-roundup/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>IE6 Must Die (along with 7 and 8)</title>
		<link>http://blogs.walkerart.org/newmedia/2009/07/17/ie6-must-die-along-with-7-and-8/</link>
		<comments>http://blogs.walkerart.org/newmedia/2009/07/17/ie6-must-die-along-with-7-and-8/#comments</comments>
		<pubDate>Fri, 17 Jul 2009 18:31:57 +0000</pubDate>
		<dc:creator>Brent Gustafson</dc:creator>
				<category><![CDATA[General]]></category>
		<category><![CDATA[Web Development]]></category>

		<guid isPermaLink="false">http://blogs.walkerart.org/newmedia/?p=872</guid>
		<description><![CDATA[One of the trending topics on Twitter currently is &#8220;IE6 Must Die&#8220;, which are mainly retweets to a blog post entitled &#8220;IE6 Must Die for the Web to Move On&#8220;.  This is certainly true, IE6 has many rendering bugs and lacks support for so many things that it is simply a nightmare to work [...]]]></description>
			<content:encoded><![CDATA[<p><img src="http://blogs.walkerart.org/newmedia/files/2009/07/iedestroy.png" alt="iedestroy" width="244" height="146" class="alignleft size-full wp-image-876" />One of the trending topics on Twitter currently is &#8220;<a href="http://twitter.com/#search?q=%22IE6%20Must%20Die%22">IE6 Must Die</a>&#8220;, which are mainly retweets to a blog post entitled &#8220;<a href="http://mashable.com/2009/07/16/ie6-must-die/">IE6 Must Die for the Web to Move On</a>&#8220;.  This is certainly true, IE6 has many rendering bugs and lacks support for so many things that it is simply a nightmare to work with.  The amount of time and money wasted in supporting this browser across the web is staggering.</p>
<p>In fact a few months ago the New Media department decided to drop support for IE6 on all future websites we create.  The last website we built with full IE6 support was the new <a href="http://artsconnected.org">ArtsConnectEd</a>, mainly because teachers tend to have little say in what browsers they can use on school computers.  However, moving forward we&#8217;re phasing out support for IE6.  It simply costs us too much time and resources for the dwindling number of users it has on our sites (currently under 10%, which is down 45% from last year and falling fast).  We&#8217;re not alone, many other sites are doing this as well.</p>
<p>However calling for the killing of IE6 ignores a bit of history as well as new problems to come.  There was a time not so long ago when all web developers wanted to be using IE6.  The goal back then was to kill off IE5.  You see, IE5 had an incorrect box model.  Padding and margins were included in a boxes width and height instead of adding to it like in standards compliant browsers.</p>
<p>This caused all sorts of layout errors, and meant hacks (like the <a href="http://brentgustafson.com/dump/dom/sbmh.html">Simplified Box Model Hack</a>) had to be used to get content to align correctly.  These hacks were so widely used that Apple was going to allow them to be used in the first version of Safari until I convinced Dave Hyatt (lead Safari dev) to take out support for it.  IE6 fixed this bug and everyone was happy (for a while anyway).</p>
<p>Going back further, IE5, even with its broken box model, was at one time the browser of choice back when IE4 was killing Javascript programmers because it didn&#8217;t support <code>document.getElementById()</code>.   IE4 only supported the proprietary <code>document.all</code> leading to a horrible fracturing of Javascript, whereas IE5 added in the JS standard we still use today.  Before people embraced IE5, cross platform JS on the web was almost non-existent, a fact I attempted to rectify by building my <a href="http://assembler.org/xlat/">Assembler</a> site in 1999.</p>
<p>The reason I bring this up is because we have a history of this behavior with regards to IE.  We yearn for the more modern versions, only to end up hating those same versions later on.  This will not change with the death of IE6.  Soon, it will be IE7 that we are trashing, and then IE8 will be the bane of our existence.</p>
<p>This only becomes more clear as we move to HTML5.  IE8 doesn&#8217;t support it, nor does it support any CSS3.  While IE8 does support many of the older standards it had been ignoring for so long, having just recently been released it is already out of date. All of the other browsers do support these advanced web technologies, but IE is the lone browser to ignore them. Once again IE is two steps behind where the web is going, and severely limits our ability to push web technology forward to everyone for many years to come.</p>
<p>So while we celebrate the death of IE6, let us not forget that there will be a new thorn in our side to take its place in short order.  IE7, you&#8217;re next.</p>
]]></content:encoded>
			<wfw:commentRss>http://blogs.walkerart.org/newmedia/2009/07/17/ie6-must-die-along-with-7-and-8/feed/</wfw:commentRss>
		<slash:comments>71</slash:comments>
		</item>
		<item>
		<title>Do one thing in April&#8230;</title>
		<link>http://blogs.walkerart.org/newmedia/2009/05/01/do-one-thing-in-april/</link>
		<comments>http://blogs.walkerart.org/newmedia/2009/05/01/do-one-thing-in-april/#comments</comments>
		<pubDate>Fri, 01 May 2009 16:37:45 +0000</pubDate>
		<dc:creator>Nate Solas</dc:creator>
				<category><![CDATA[Announcements]]></category>
		<category><![CDATA[Conference Notes]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[mw2009]]></category>

		<guid isPermaLink="false">http://blogs.walkerart.org/newmedia/?p=831</guid>
		<description><![CDATA[&#8230; blog about it in May!

Museums and the Web 2009 wrapped up with a challenge to all the inspired delegates: use the energy and ideas generated here to get one thing done in April.  (The idea being that many small steps build momentum, and it&#8217;s too easy to ignore the small upgrades we should constantly [...]]]></description>
			<content:encoded><![CDATA[<p>&#8230; blog about it in May!</p>
<h2><a href="http://collections.walkerart.org/search.html?onview=on&amp;searchstring=flavin"><img class="size-full wp-image-824 alignleft" style="margin-right: 20px" src="http://blogs.walkerart.org/newmedia/files/2009/05/onview.png" alt="onview" width="157" height="250" /></a></h2>
<p>Museums and the Web 2009 wrapped up with a challenge to all the inspired delegates: use the energy and ideas generated here to <a href="http://museum-api.pbworks.com/The-MW2009-challenge">get one thing done in April</a>.  (The idea being that many small steps build momentum, and it&#8217;s too easy to ignore the small upgrades we should constantly be pushing out.)</p>
<p>Yesterday I pushed out a few small upgrades to our aging collection site:</p>
<h2>You can now limit your search to <a href="http://collections.walkerart.org/search.html?onview=on">objects that are On View</a></h2>
<p><a href="http://collections.walkerart.org/search.html?onview=on"></a>What <a href="http://collections.walkerart.org/search.html?onview=on&amp;searchstring=flavin">works by Dan Flavin</a> can you come see right now?</p>
<h2><img class="alignright size-full wp-image-825" src="http://blogs.walkerart.org/newmedia/files/2009/05/browser_search.png" alt="browser_search" width="252" height="307" />OpenSearch capable</h2>
<p>Can&#8217;t get enough of our collection?  Add it to your browser’s built-in search box!  When you&#8217;re on the Collection site, you should be able to pull down your browser&#8217;s search field and add &#8220;Walker Art Center&#8221;.</p>
<p>Developers (<a href="http://museumpipes.wordpress.com/">Piotr</a>!): you can now use the Walker collection in your Yahoo Pipes tool without having to scrape the results!  Not an API (yet), but a good step.  Check out the <a href="http://collections.walkerart.org/opensearch.html?query=flavin&amp;page=1&amp;onview=on&amp;online_only=off">XML</a> for ideas.</p>
<h2>Bring it all together:</h2>
<p>You&#8217;re a busy person.  You&#8217;d love to come see Chuck Close&#8217;s <em>Big Self-Portrait</em>, and you know the Walker&#8217;s got it in their collection, but you see it&#8217;s not on view.  You don&#8217;t have time to check our website every day, so how will you ever know when it goes on display?  Easy:  <a href="http://collections.walkerart.org/search.html?searchstring=chuck%20close%201969.16&amp;onview=off">build a search</a> that finds <em>Big Self-Portrait</em>, then <a href="http://collections.walkerart.org/search.html?searchstring=chuck%20close%201969.16&amp;onview=on">turn on the &#8220;On View&#8221; flag</a>.  The object disappears (not on view), but you can subscribe to the OpenSearch RSS feed for this query (click the <img style="margin:0px;border:0px" src="http://collections.walkerart.org/images/rss.png" alt="rss" vspace="0" width="14" height="14" /> icon).  Now, when <em>Big Self-Portrait</em> is available to see in the galleries, the object will show up in your RSS reader!  (note: I picked this painting randomly.  I make no guarantee about seeing it in the galleries any time soon.  :)</p>
<p>So, baby steps.  Get one things done that opens more doors.</p>
<p>#didonethinginapril (I tag Andrew at the MIA to get one thing done in May!)</p>
]]></content:encoded>
			<wfw:commentRss>http://blogs.walkerart.org/newmedia/2009/05/01/do-one-thing-in-april/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Recent enhancements to mnartists.org</title>
		<link>http://blogs.walkerart.org/newmedia/2009/04/03/recent-enhancements-to-mnartistsorg/</link>
		<comments>http://blogs.walkerart.org/newmedia/2009/04/03/recent-enhancements-to-mnartistsorg/#comments</comments>
		<pubDate>Fri, 03 Apr 2009 21:30:39 +0000</pubDate>
		<dc:creator>Justin Heideman</dc:creator>
				<category><![CDATA[Web Development]]></category>
		<category><![CDATA[mnartists.org]]></category>

		<guid isPermaLink="false">http://blogs.walkerart.org/newmedia/?p=753</guid>
		<description><![CDATA[For the past month or two, we have been working on changes to mnartists.org. We deployed some of these changes several weeks ago, and just deployed even more now. I thought I would take some time to highlight the enhancements and new goodies.
Homepage
The first change you&#8217;ll notice when visiting the site is that the home [...]]]></description>
			<content:encoded><![CDATA[<p>For the past month or two, we have been working on changes to <a href="http://mnartists.org/home.do">mnartists.org</a>. We deployed some of these changes several weeks ago, and just deployed even more now. I thought I would take some time to highlight the enhancements and new goodies.</p>
<h4>Homepage</h4>
<p>The first change you&#8217;ll notice when visiting the site is that the home page got an overhaul. The rotators for New Artwork and Featured Collections were changed to display images to the full-size of their boxes and they animate smoother. This means sometimes cropping work, but we think it&#8217;s a trade-off worth making.</p>
<p>Articles are also displayed with a three-tier hierarchy, allowing the site to call recent writing out more prominently, even though we feature six instead of 10. The sidebar on the homepage has also been reorganized, bringing the mnartists.org blog to the top and adding links to the <a href="http://www.facebook.com/pages/Minneapolis-MN/mnartistsorg/40989003319">Facebook</a> and <a href="http://twitter.com/mnartistsdotorg">Twitter</a> profiles for mnartists.org.</p>
<div id="attachment_754" class="wp-caption alignleft" style="width: 342px"><img class="size-medium wp-image-754" src="http://blogs.walkerart.org/newmedia/files/2009/04/mna_homepage_new-450x337.jpg" alt="mna_homepage_new" width="332" height="248" /><p class="wp-caption-text">The revised homepage.</p></div>
<div id="attachment_759" class="wp-caption alignleft" style="width: 326px"><strong></strong><strong><img class="size-medium wp-image-759" src="http://blogs.walkerart.org/newmedia/files/2009/04/mna_articles-450x370.jpg" alt="Revised article page" width="316" height="260" /></strong><p class="wp-caption-text">Revised article page</p></div><br />
<br class="clear" /></p>
<p><br class="clear" /></p>
<h4>Articles</h4>
<p>Articles got some attention in several ways. First, we changed the way images are displayed by adding a larger expanding gallery at the top of each article, rather than having small images thumbnails listed down the left side. On the back end for editors, we also added an enhanced editor (tiny mce) to allow for richer control over formatting and even embedding other media.</p>
<p><strong>Social media Sharing<br />
</strong>Across many areas of the site, you&#8217;ll now see a link to Share this article/artwork/collection/event. Using much of the same <a href="http://blogs.walkerart.org/newmedia/2008/12/22/enabling-users-to-share-content-to-social-media-sites/">code we developed for the Walker Calendar</a>, sharing is now easier on mnartists.org. We connect with Facebook, Twitter, Myspace, Delicious, Google Bookmarks, and Yahoo Bookmarks, as well as rolling in email links in a few places.</p>
<p><div id="attachment_764" class="wp-caption alignnone" style="width: 536px"><img class="size-full wp-image-764" src="http://blogs.walkerart.org/newmedia/files/2009/04/mna_sharing.jpg" alt="mna_sharing" width="526" height="98" /><p class="wp-caption-text">The new sharing links.</p></div>
<h4>Search</h4>
<p>The one change that will probably make everyone cry tears of joy is the search results refinement. We&#8217;ve heard lots of complaints about the search, not wholly unfounded. The search actually works pretty good, but the simple search weights everything more or less equally. If you search for someone&#8217;s name, hoping to just get their artist page, it will be in the results, but there might be other things that rank higher.</p>
<p>The revised search result page lets you change your simple search into an advanced search, using tabs above the results to select the type of resource you want to search for. This is very similar to what google does with their search results refinements (web, images, video, maps, etc.).</p>
<div id="attachment_767" class="wp-caption alignleft" style="width: 280px"><img class="size-medium wp-image-767" src="http://blogs.walkerart.org/newmedia/files/2009/04/mna_search_before-450x382.jpg" alt="Old style search results" width="270" height="229" /><p class="wp-caption-text">Old style search results</p></div>
<div id="attachment_766" class="wp-caption alignleft" style="width: 280px"><img class="size-medium wp-image-766" src="http://blogs.walkerart.org/newmedia/files/2009/04/mna_search_after-450x382.jpg" alt="New search results with refinements." width="270" height="229" /><p class="wp-caption-text">New search results with refinements.</p></div><br />
<br class="clear" /></p>
<p><br class="clear" /></p>
<h4>Artist Pages</h4>
<p>Artist pages also got an overhaul with two big changes. First, images for each artwork will display at a new, larger size, about 519px tall and/or 520px wide. For artworks with more than one image associated, a gallery rotating gallery will cycle through the images. Previously, if an artwork had more than one image associated, only the first would show up, and the rest would be listed in the &#8220;Related Media&#8221; list.</p>
<p><div id="attachment_770" class="wp-caption alignleft" style="width: 280px"><img class="size-medium wp-image-770" src="http://blogs.walkerart.org/newmedia/files/2009/04/brent-before-450x407.png" alt="Old artist homepage." width="270" height="244" /><p class="wp-caption-text">Old artist homepage.</p></div>
<div id="attachment_771" class="wp-caption alignleft" style="width: 280px"><img class="size-medium wp-image-771" src="http://blogs.walkerart.org/newmedia/files/2009/04/brent-after-450x391.png" alt="Revised artist page." width="270" height="235" /><p class="wp-caption-text">Revised artist page.</p></div><br />
<br class="clear" /></p>
<p>Secondly, we changed the way Related Media works. Now, it is simply &#8220;Media List&#8221; lists every type of media associated with an artwork. More importantly, for non-image media, such as video and audio, we embed the media in the actual page. So if you upload a quicktime file, the quicktime embed code will be generated and put right into the page. MP3 audio files will be played with the <a href="http://www.longtailvideo.com/">jwplayer</a> flash player, making audio on the site a lot more nifty. We&#8217;re using the excellent <a href="http://www.malsup.com/jquery/media/">jquery.media</a> plugin to do this.</p>
<p>This approach to handling media isn&#8217;t without some issues, but given the variety of media already on the site and our resources to work on it, this is the best solution. We are looking at making more substantial changes to this in the future, but this is a good incremental improvement.</p>
<p><div id="attachment_776" class="wp-caption alignleft" style="width: 280px"><img class="size-medium wp-image-776" src="http://blogs.walkerart.org/newmedia/files/2009/04/heather_video_before-450x337.png" alt="Artwork with video before changes." width="270" height="202" /><p class="wp-caption-text">Artwork with video before changes.</p></div>
<div id="attachment_775" class="wp-caption alignleft" style="width: 191px"><img class="size-medium wp-image-775" src="http://blogs.walkerart.org/newmedia/files/2009/04/heather_video_after-301x450.png" alt="Artwork with video after changes. (Two video files attached)" width="181" height="270" /><p class="wp-caption-text">Artwork with video after changes. (Two video files attached)</p></div><br />
<br class="clear" /></p>
<p>The image size and media enhancements have also been applied to the collections area of the site.<br />
<br class="clear" /></p>
<h4>Editing text</h4>
<p>Another change we made a month ago was adding a visual editor to various form fields on the site. Prior to the change, users could only enter a very limited selection of markup to entries, [b] for bold, [i] for italic, and [a] for a link. We&#8217;ve eliminated that and replaced it with the new editor (tiny_mce), which allows for bold, italic, underline, unordered lists, and links. While seemingly simple, it was actually quite a challenge to deal with both the legacy code and the new formatting. The text actually goes through several transformations between the editor, the database, and being displayed again. Keeping everything consistent is a non-trivial pile of regular expressions. </p>
<p><div id="attachment_778" class="wp-caption alignnone" style="width: 539px"><img src="http://blogs.walkerart.org/newmedia/files/2009/04/editor.jpg" alt="The new visual editor." width="529" height="380" class="size-full wp-image-778" /><p class="wp-caption-text">The new visual editor.</p></div>
<p>One thing that we will have to keep an eye on is users pasting in text from Microsoft Word. Word tends to shove a bunch of garbage pseudo-html into the clipboard, and when pasting, it can be difficult to filter out. The editor has a button to Paste from word (with the blue W) that helps.  </p>
<h4>Any issues?</h4>
<p>If you notice any problems with the site, please let our <a href="mailto:info@mnartists.org">community manager</a> or myself know. Bugs may crop up, and we do fix things.</p>
]]></content:encoded>
			<wfw:commentRss>http://blogs.walkerart.org/newmedia/2009/04/03/recent-enhancements-to-mnartistsorg/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Enabling users to share content to social media sites</title>
		<link>http://blogs.walkerart.org/newmedia/2008/12/22/enabling-users-to-share-content-to-social-media-sites/</link>
		<comments>http://blogs.walkerart.org/newmedia/2008/12/22/enabling-users-to-share-content-to-social-media-sites/#comments</comments>
		<pubDate>Mon, 22 Dec 2008 21:57:20 +0000</pubDate>
		<dc:creator>Justin Heideman</dc:creator>
				<category><![CDATA[Social Networking]]></category>
		<category><![CDATA[Web Development]]></category>

		<guid isPermaLink="false">http://blogs.walkerart.org/newmedia/?p=707</guid>
		<description><![CDATA[Last week we made a small change to our online calendar, adding social media sharing features. This means it is easier for readers of our online calendar to tell their friends and contacts about events. In our calendar, if you click the &#8220;share this&#8221; button, it slides out this drawer:

There are a number of pre-made [...]]]></description>
			<content:encoded><![CDATA[<p>Last week we made a small change to our <a href="http://calendar.walkerart.org">online calendar</a>, adding social media sharing features. This means it is easier for readers of our online calendar to tell their friends and contacts about events. In our calendar, if you click the &#8220;share this&#8221; button, it slides out this drawer:</p>
<p><a href="http://blogs.walkerart.org/newmedia/files/2008/12/share_this.png"><img class="alignnone size-full wp-image-708" src="http://blogs.walkerart.org/newmedia/files/2008/12/share_this.png" alt="" width="500" height="127" /></a></p>
<p>There are a number of <a href="http://sharethis.com/">pre-made</a> <a href="http://www.addthis.com/bookmark.php">DHTML</a> <a href="http://bookmarkit.org/">widgets</a> out there that are easy to use, but don&#8217;t provide quite the user experience we would like to have. Furthermore, they don&#8217;t share the content as cleanly and aren&#8217;t event specific. So we made our own functionality. It isn&#8217;t rocket surgery, but some notes on what we did may prove useful for others.</p>
<p><strong>Sharing to calendars</strong><br />
Because this is an online calendar, sharing events to other calendars are very important. We already have an <a href="//calendar.walkerart.org/ical.wac?id=all">iCal feed for our calendar</a>, and it is already set up to share specific events rather than all events, we just hadn&#8217;t been using that feature. The new sharing widget does so, simply by passing the proper event ID to our iCal page. A user can download this .ics file and it should work in Outlook, Sunbird, or iCal.</p>
<p>We also added sharing to <a href="http://calendar.google.com/">google calendar</a>. Google has an <a href="http://www.google.com/googlecalendar/event_publisher_guide_detail.html">event publisher guide</a> that documents how to share events to gCal. Compared to most other sharing solutions, gCal is more complicated. The main thing we had trouble with was formatting the date properly. Google prefers the date in what it calls &#8220;UTC format&#8221;, but I cannot find documentation anywhere showing what google uses is actually UTC format. What UTC format actually appears to be is the <a href="http://en.wikipedia.org/wiki/ISO_8601">ISO 8601</a> time formatted without any punctuation.  This is very similar to what the <a href="http://tools.ietf.org/html/rfc2445">iCal format</a> uses internally. Thankfully, since we were already calculating this for the iCal format, <a href="http://blogs.walkerart.org/newmedia/authors/nate/">Nate</a> was able to easily pass me this info for each event. With that, it is simply a matter of putting together the various components of the URL:</p>
<p><code></p>
<pre>gcalURL = 'http://www.google.com/calendar/event?action=TEMPLATE&amp;text='+encodeURIComponent(share_eventTitle);
gcalURL += '&amp;dates='+startDate+"/"+endDate;
gcalURL += "&amp;sprop=website:"+encodeURIComponent(location.href)+"&amp;sprop=name:Walker%20Art%20Center";
gcalURL += "&amp;details="+encodeURIComponent(share_eventDesc);
gcalURL += "&amp;trp=true"
if (theLocation){
	gcalURL += "&amp;location="+encodeURIComponent(theLocation);
}
window.open(gcalURL);
return false;</pre>
<p></code></p>
<p><strong>Sharing to social media sites</strong><br />
MySpace and Facebook both have specifications for how to share events to each of them, documented <a href="http://www.myspace.com/Modules/PostTo/Pages/?u=URL">here</a> and <a href="http://www.facebook.com/share_partners.php">here</a>, respectively. For Facebook, it is important to modify your page to include the meta tags it requests. When you share to Facebook, it doesn&#8217;t pass a description or image via the URL. Rather, Facebook scrapes the referred page to ascertain the description and images. Using the Meta tags gives much better results than whatever Facebook&#8217;s scraper comes up with. Most of the time, if you rely on their scraper, it will come up with some chrome images from your site rather than actual content images. </p>
<p>MySpace doesn&#8217;t scrape the page like facebook, so it&#8217;s important to construct a friendly description, with an image, if you can. We put a linked image along with the first few sentences of text for the description we pass to MySpace. Here is the format MySpace uses:<br />
<code><br />
http://www.myspace.com/index.cfm?fuseaction=postto&amp;u=YOURURL&amp;t=YOURTITLE&amp;c=YOURDESCRIPTION&amp;l=3<br />
</code></p>
<p>We also &#8220;share&#8221; to twitter. There isn&#8217;t really sharing per se, on twitter, but you can pre-assemble a tweet for someone. Simply pass someone to <code>http://twitter.com/home/?status=YOURTWEET</code>. We assmble the event title, a reply to <a href="http://twitter.com/walkerartcenter">our twitter account</a>, and a twitter-friendly shortened URL. Like this: &#8220;2008 British Television Advertising Awards @walkerartcenter &#8211; http://bit.ly/3c60xK&#8221;. </p>
<p>To get the friendly URL, we&#8217;re using <a href="http://bit.ly/">bit.ly</a>, one of the many URL shortening services available. However, bit.ly has a handy, well documented, API that does <a href="http://www.clientcide.com/wiki/cnet-libraries/06-request/00-jsonp">JSONP</a>, allowing us to get around cross-site scripting issues. </p>
<p><strong>Sharing to bookmarking sites</strong><br />
Sharing to Bookmarking sites like <a href="http://delicious.com">delicious</a> (formerly del.icio.us, we miss the old URL) is quite simple. These are the formats for Delicious, Google Bookmarks and Yahoo Bookmarks, respectively:<br />
<code><br />
http://delicious.com/save?jump=yes&amp;url=YOURURL&amp;title=YOURTITLE<br />
http://www.google.com/bookmarks/mark?op=edit&amp;bkmk=YOURURL&amp;title=YOURTITLE<br />
http://bookmarks.yahoo.com/toolbar/savebm?u=YOURURL&amp;t=YOURTITLE&amp;opener=bm&amp;ei=UTF-8<br />
</code><br />
Yahoo Bookmarks like to be opened in a pop-up window, but it isn&#8217;t strictly necessary. Always remember to urlencode text that is being passed into the URL, since there are many reserved characters in the URL. Javascript provides the <code> encodeURIComponent</code> function to do this. </p>
]]></content:encoded>
			<wfw:commentRss>http://blogs.walkerart.org/newmedia/2008/12/22/enabling-users-to-share-content-to-social-media-sites/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Installing a Lighttpd proxy</title>
		<link>http://blogs.walkerart.org/newmedia/2008/11/22/installing-a-lighttpd-proxy/</link>
		<comments>http://blogs.walkerart.org/newmedia/2008/11/22/installing-a-lighttpd-proxy/#comments</comments>
		<pubDate>Sat, 22 Nov 2008 17:39:00 +0000</pubDate>
		<dc:creator>Nate Solas</dc:creator>
				<category><![CDATA[Hardware]]></category>
		<category><![CDATA[Tools]]></category>
		<category><![CDATA[Web Development]]></category>

		<guid isPermaLink="false">http://blogs.walkerart.org/newmedia/?p=694</guid>
		<description><![CDATA[It had been a slow build, but an incident a few weeks ago made it finally clear: the Walker website was becoming a victim of its own success.  A post on the Teens site contained a picture of the Joker for the then-upcoming Batman movie, and as Halloween approached we found ourselves on the front [...]]]></description>
			<content:encoded><![CDATA[<p>It had been a slow build, but an incident a few weeks ago made it finally clear: the Walker website was becoming a victim of its own success.  A <a href="http://teens.walkerart.org/2008/01/">post on the Teens site</a> contained a picture of the Joker for the then-upcoming Batman movie, and as Halloween approached we found ourselves on the front page of Google image search as people began looking for costume ideas.  The exponential traffic was crippling our web server:</p>
<p><a href="http://blogs.walkerart.org/newmedia/files/2008/11/teens_spike.png"><img class="alignnone size-full wp-image-695" src="http://blogs.walkerart.org/newmedia/files/2008/11/teens_spike.png" alt="" width="405" height="138" /></a></p>
<p>The biggest problem was simply that Apache is heavy.  It&#8217;s resource-intensive, especially when you are running several modules as we were &#8211; PHP, proxy, cache, etc.  The teens site is especially difficult since it runs as a combination of a blog (PHP on Apache 2) and .wac pages (mod_perl &amp; Axkit).  Every hit to the Joker post &#8211; even if the page was cached &#8211; would tie up a number of Apache processes as it served the style sheets, images, and javascript to support the page.  We were reaching our MaxClients setting but unable to raise it without running out of memory for our other more intensive servers (mod_perl and postgres, I&#8217;m looking at you&#8230;).</p>
<p>As this diagram shows, it&#8217;s nothing but Apache servers, and it just wasn&#8217;t scaling to meet our current demand.</p>
<p><a href="http://blogs.walkerart.org/newmedia/files/2008/11/wac_old_servers.png"><img class="alignnone size-full wp-image-696" src="http://blogs.walkerart.org/newmedia/files/2008/11/wac_old_servers.png" alt="" width="500" height="177" /></a></p>
<p>The approach was two-fold: some quick auditing and re-writing of the worst offending .wac pages&#8217; SQL to speed up the slow pages, and yet another web server in front of everything.  It was a no brainer to pick Lighttpd, or &#8220;Lighty&#8221;.  It&#8217;s written to do one thing &#8211; serve static content &#8211; and do it extremely fast.  Fortunately it can also proxy requests, so it was a pretty simple matter to reassign some ports and write a few rules to route all requests through Lighty.</p>
<p><a href="http://blogs.walkerart.org/newmedia/files/2008/11/wac_new_servers.png"><img class="alignnone size-full wp-image-697" src="http://blogs.walkerart.org/newmedia/files/2008/11/wac_new_servers.png" alt="" width="500" height="206" /></a></p>
<p>The end result is astonishing.  Our server hums along happily under even the most intense traffic we can throw at it (the email blast for the <a href="http://filmvideo.walkerart.org/detail.wac?id=4737&amp;title=Upcoming%20Programs">British Television Advertising Awards</a>) and doesn&#8217;t even start to complain.  Moving the bulk of the requests to the extremely fast and resource-light server meant we could devote more resources to quickly processing the slow pages (mod_perl).  Between the SQL tuning and the extra resources, the bulk of these pages are now served between 2 and 10(!!!) times faster!</p>
<p>The lesson here, for anyone with an Apache server creaking and groaning under increased traffic, is to stop waiting and install Lighty.  If your site is PHP-based, you can run this as a fast CGI module from Lighty and do away with Apache altogether.  You can also use Lighty to stream (and &#8220;scrub&#8221;!) flv and mp4 video files.  (I&#8217;m using both of these techniques for the <a href="http://ace2.artsconnected.org/">new ArtsConnectEd</a>.)</p>
<p>The only caveat: be careful as you look for examples on the web.  Remarkably, it seems there are many confused webmasters who expect to see a performance boost by putting Lighty <em>behind</em> their struggling Apache.  This will not help at all, and in fact will probably make things worse.  Lighty has to be first in the chain to take the load off Apache.</p>
<p>Enjoy the speed!  I know our server is enjoying the breathing room!</p>
]]></content:encoded>
			<wfw:commentRss>http://blogs.walkerart.org/newmedia/2008/11/22/installing-a-lighttpd-proxy/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Upgrading to WordPress-MU from regular WordPress</title>
		<link>http://blogs.walkerart.org/newmedia/2008/10/08/upgrading-to-wordpress-mu-from-regular-wordpress/</link>
		<comments>http://blogs.walkerart.org/newmedia/2008/10/08/upgrading-to-wordpress-mu-from-regular-wordpress/#comments</comments>
		<pubDate>Wed, 08 Oct 2008 17:22:50 +0000</pubDate>
		<dc:creator>Justin Heideman</dc:creator>
				<category><![CDATA[Blogs]]></category>
		<category><![CDATA[Web Development]]></category>

		<guid isPermaLink="false">http://blogs.walkerart.org/newmedia/?p=587</guid>
		<description><![CDATA[Since this blog started in 2005, we&#8217;ve steadily grown in readership and the number of blogs hosted at the Walker. We had 10 unique blogs, which was a hassle to maintain and upgrade. Our blogs all shared a central installation of WordPress, but used different databases. This meant that the users between blogs were unique: [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://blogs.walkerart.org/newmedia/files/2008/10/wordpress_logo.jpg"><img class="alignright size-medium wp-image-594" src="http://blogs.walkerart.org/newmedia/files/2008/10/wordpress_logo-450x450.jpg" alt="" width="315" height="315" /></a>Since this blog started in 2005, we&#8217;ve steadily grown in readership and the number of blogs hosted at the Walker. We had 10 unique blogs, which was a hassle to maintain and upgrade. Our blogs all shared a central installation of <a href="http://wordpress.org/">WordPress</a>, but used different databases. This meant that the users between blogs were unique: I could have different passwords and profiles on each blog, keeping them in sync was time consuming.</p>
<p>Faced with adding another blog (more on that in a couple weeks), we knew that we couldn&#8217;t continue to grow our haphazard system any more. We bit the bullet and switched to <a href="http://mu.wordpress.org/">WordPress MU</a> (WPMU), which solves many of the problems over standalone WordPress. I&#8217;ll enumerate the benefits:</p>
<ul>
<li>Shared users between all blogs</li>
<li>Centralized location for files</li>
<li>Can activate plug-ins across all blogs</li>
<li>Easy to add new blogs</li>
<li>Allows us to set up caching</li>
</ul>
<p>In the past, we had resisted going to WPMU because it wasn&#8217;t keeping pace with standalone WordPress in terms of updates. At the same time, we hadn&#8217;t updated in a while, and were still using WordPress 2.3.3, when 2.5 and 2.6 added quite a bit, especially better image management. Around the 2.5.2 release of WordPress, the MU trunk was finally in close sync with standalone. No more excuses.</p>
<p><strong>How MU Works</strong></p>
<p>WPMU is very similar to regular WordPress. In fact, there are really only a handful of files that are any different than regular WordPress. The functions that WPMU adds are mostly to deal with the mutiple blog IDs, creating and delting blogs, and dealing with the more complicated permissions. Two of the more handy function it provides are<em> switch_to_blog( $new_blog )</em> and <em>restore_current_blog()</em>, which let you switch to a different blog, use the proper Loop queries, and then go back to the original blog. This makes it a lot easier to deal with getting information from multiple blogs, an otherwise complicated procedure.</p>
<p>If you look at the database model, WPMU keeps centralized tables for users and blog meta information. Each new blog created gets a set of it&#8217;s own tables.</p>
<p><strong>Upgrading</strong></p>
<p>Upgrading to WPMU is not a simple process. In fact, there&#8217;s no specific procedure or easy script for it. The main problem is that if you have multiple blogs, your user IDs and post IDs are going to collide. That is, on the New Media blog, my user ID might be 7, but on OffCenter, it is 23, and user ID 7 is someone else. This prevents us from doing a straight SQL import. We&#8217;d need a script that could interact with all our existing databases, figure out the conflicts, and then create a new centralized list of users (more on this in a minute).</p>
<p>Additionally, importing posts wouldn&#8217;t work either, because with a new users table, all the user IDs are likely to be different. Thankfully, WordPress does provide functionality to export posts on a per-user basis, and then when you import them, assign them to new users. I did a little testing and this worked well enough, but I hacked wordpress and wrote a small script to automate this and dump all the WordPress Export files (WXR) to the filesystem. I was able to run this script and grab all the export files.</p>
<p>As for importing the users, I dumped the <em>wp_users</em> and <em>wp_usermeta</em> table from each database, renamed them, and brought them all into a single database. I wrote a script that iterated through all the databases, matching users based on their login name. Thankfully, we only had two users that shared the same login names, so resolving that conflict was easy. I would collect all the information about a user spread across all the blogs, and then add it correctly to a new wp_users and wp_usermeta table. The trick was to know ahead of time what the new blog IDs in WPMU would be, then re-map the existing wp_user_level and wp_capabilities info to <em>wp_3_user_level</em>, and <em>wp_capabilities</em>, for instance.</p>
<p><strong>Cursed Text Encoding</strong></p>
<p>Another problem we ran into was incompatabilites with text encoding. Many of our entries had invalid characters in them. For compatability reasons with AxKit (which powers most of the Walker site) we&#8217;ve been using ISO-8859-1 encoding, which has a fairly limited range of characters. WordPress doesn&#8217;t do such a good job of forcing non-standard characters to be transformed into HTML entities, so anytime there was a proper apostrophe, curly quote or emdash, a freaky character would make it&#8217;s debut. Despite this, the posts continuted to work, the database threw a couple of warnings, but it continued to work.</p>
<p>The problem came with importing the exported WXR files. WordPress&#8217; importer is very strict, and it would hang on any of these invalid characters. After trying multiple things to filter them out in the export or the database, I ended up using a Text Factory in BBEdit to replace them. Additionally, BBEdit has a wonderful command to &#8220;Zap Gremlins&#8221;, or take out weird characters in text. A few minutes of batch processing later, and the import files were clean.</p>
<p>Importing the cleaned WXR files (267 of them) was a tedious process that had to be done by hand, but allowed us to catch any errors and make sure the author assignment worked correctly.</p>
<p><strong>VHOST vs. Sub-directory</strong></p>
<p>WPMU can work in two different URL configurations. One is sub-domain based: joe.mydomain.com, steve.mydomain.com, etc. The other is sub-directory based: blogs.mydomain.com/steve, blogs.mydomain.com/joe. You have to decide what configuration to use when you install WPMU. We use a sub-directory based system for our blogs, but a couple other sites, <a href="http://teens.walkerart.org">teens.walkerart.org</a> and <a href="http://air.walkerart.org/">air.walkerart.org</a>, are also blogs, but use a sub-domain.</p>
<p>This would seem to leave us at somethinig of an impasse. All of my research confirmed there was no way to have both a sub-domain and sub-directory based URL structure. Several people on the WPMU forums were <a href="http://mu.wordpress.org/forums/topic.php?id=8788">looking for a solution</a>, but none was to be found.</p>
<p>However, there is a way, and it&#8217;s a thorougly good hack. Rather than try to force WPMU to work with both, we just let WordPress deal with sub-directory based URLs. Instead, we use a mod_rewrite to proxy the site to the sub-domain URL (your server must have mod_rewrite AND mod_proxy installed). The theme for the site has to be hacked to output all the links as the sub-domain URLs, even though it&#8217;s being served out of a sub-directory configuration. The teens site is a good example. If you visit <a href="http://blogs.walkerart.org/teens/">blogs.walkerart.org/teens/</a>, the site works just fine. Here&#8217;s the mod_rewrite that would go in the httpd.conf or .htaccess:</p>
<p><code>RewriteEngine On<br />
RewriteRule ^(.*)$ http://blogs.walkerart.org/teens$1 [P]<br />
</code><br />
All of the links will take you to a teens.walkerart.org URL. To re-write the URLs, you must replace all the <em>get_permalink()</em> function calls with a custom function that returns the sub-domain URL. And if you use any other functions, such as wp_get_pages or wp_list_cats() to output any links, you must again replace the sub-directory URL with the sub-domain URL. Here&#8217;s an example of the useful code:<br />
<code> </code></p>
<pre>define('BASE_URL','blogs.walkerart.org/teens/'); //original sub-directory URL
define('REWRITE_URL',"teens.walkerart.org/"); // new sub-domain URL
echo str_replace(BASE_URL,REWRITE_URL, wp_list_pages('hierarchical=0&amp;title_li=&amp;echo=0') );

//takes a given post or page ID and returns the permalink for a teens.walkerart.org permalink
//this is used for the teens site that's run on wordpress mu in a subdirectory configuration,
//but uses mod_rewrite and mod_proxy to relay URLs to a different domain
function get_teens_permalink($id){
    $perma = get_permalink($id);
    return str_replace(BASE_URL,REWRITE_URL,$perma);
}</pre>
<p><strong>Other useful bits</strong></p>
<p>Aside from the sub-directory/sub-domain problems, there were not any other huge issues with our upgrade. A few little things in our themes had to be changed. Author pages needed to be made WPMU aware. Styles needed to be added for the galleries and captions that WP 2.5 and 2.6 added.</p>
<p>We also replaced <a href="http://blogs.walkerart.org/">blogs.walkerart.org</a> with a page powered by WPMU, rather than the old axkit-based aggregation that was there previously. This is a custom theme that queries all the blogs and displays the current aggregation. The aggregated RSS feeds we offer was something we also needed to keep, but WPMU doesn&#8217;t offer a built-in way to do that. Thankfully, there is a plugin, <a href="http://wpmudev.org/project/WPMU-Sitewide-Feed-Plugin---ITDamager">WPMU-Sitewide-Feed</a>, that takes care of it. The plugin is no longer maintained, but there is a version available that I was able to get to work with some slight modifications.</p>
<p>Another plug-in we&#8217;re now using is <a href="http://jamesmckay.net/code/comment-timeout/">Comment Timeout</a>. This plug-in is pretty simple. It closes comments on old and inactive posts to cut down on spam. It is a little smart, in that it won&#8217;t close comments on a &#8220;popular&#8221; post that has recent activity. In our old setup, we had been using the wonderful Spam Karma plugin to take care of spam. It worked really well, but it&#8217;s no longer under development and is not WPMU compatible. Instead, we&#8217;ve switched to using <a href="http://defensio.com/">Defenseio</a>, which is a more free and potentially smarter competitor to Akismet. Our filter is still being trained, but I have high hopes.</p>
]]></content:encoded>
			<wfw:commentRss>http://blogs.walkerart.org/newmedia/2008/10/08/upgrading-to-wordpress-mu-from-regular-wordpress/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
	</channel>
</rss>
