battis.net and I'm all out of bubble gum…

So many of these entries are really just an attempt to make sure that the next time I go searching for an answer to some question, by gum there will be a search result. That is: I write up things that I couldn’t find an easy answer to, so that someone else reaps the benefit of my suffering.

But, sometimes, it’s just about admitting that I’m dumb.

Case in point: I’ve been grousing on Twitter about a number of things in the past week, one of which was that, for much of the fall, many of my Google calendars have been showing up in iCal doubled. Every event shows up twice. Sometimes more than that, when Google has a hiccup. And birthdays from my contacts have shown up as many as 34 times — no joke: I counted. But the core problem has been that I’ve been seeing many of my Google calendars twice in iCal. It makes me feel busy, but otherwise it serves no purpose.

And then it hit me this evening, as I was looking at the list of calendars in iCal: with the launch of Mountain Lion, Apple revised how it handled Mail, Contacts & Calendars — so much so, in fact, that they created a separate prefpane for it. That connects to iCal, Mail and the Address Book pretty transparently. Before that upgrade, to see secondary Google calendars (the ones other than your primary calendar — a distinction about which I have some more grousing to do at a later date), you had to enable calendar delegates in the Accounts section of the iCal preferences. It was messy, and ugly (you had both the delegate and the calendar nested in the delegate… for each and every calendar). But it worked.

I never turned off the delegates when Apple upgraded to Mail, Contacts & Calendars. So I was seeing both the delegate calendar and the calendar associated with my Google account in Mail, Contacts & Calendars.

For dumb.

I just turned off delegates and everything got better. Except maybe those duplicated birthdays… we’ll see what happens with them.

December 9th, 2012

Posted In: How To

Tags: , , , , ,

Because I can’t stop playing with something that already works fine, I’ve continued to tweak my syllabus-generating spreadsheet. With the addition of an external script, I’m able to link directly to a specific cell in the spreadsheet — letting me share a link to my syllabi that takes people not just to the overall document, but to the current day (or the next day that’s closest to the current day) in the syllabus. The script could be used to link to any cell in any published Google Spreadsheet, with the caveat that if there are multiple cells with that match that value, the link will take you to the first appearance of the value in the spreadsheet.

$url = $_REQUEST["url"];
$key = $_REQUEST["key"];
$anchor = $_REQUEST["anchor"];
 
$html = file_get_contents($url);
$output = str_replace($key, "<a name="$anchor"></a>$key", $html);
$output = str_replace("href='/", "href='https://docs.google.com/", $output);
 
header("Content-Type: text/html");
echo $output;

This takes a GET URL of the format http://server/script?url=[url]&key=[key]&anchor=[anchor]#[anchor], where…

  • The URL is the URL of the published Google spreadsheet location.
  • The key is the text to search for in the spreadsheet (so you could link to anything in the published HTML, but it’s easiest to link to a unique value in a cell).
  • The anchor is the name of the HREF anchor you will be creating (note that you then need to, well… link to that anchor).
Enjoy. You can try it out on my server:
URL:
Key:
Anchor:

Nota Bene: You have to add your own link to the anchor — this is a quick, slapped-together connection to my script.

Update: I was sitting here staring at the script, trying to figure out why I hadn’t put the anchor tag around the key, and then I realized what’s going on. The key (on my spreadsheet) is formatted to be white text on a white background (Rothko-style, if you will). If it gets converted to an anchor, its styling is affected and (without more work in the script) it turns blue and underlined. Lame. So… the anchor goes before the key, so the key’s CSS style won’t be affected.

This is why you document your code. Even when it’s short.

October 31st, 2012

Posted In: How To

Tags: , , , , ,

I had hoped that someone else would have put together a handy flyer to explain better options for sharing files than sending email attachments. But in a casual search of the web, none came up. This is my initial pass at the problem of presenting the Web 2.0 and the cloud to folks who are comfortable with email, but who don’t feel confident in their use of other technologies.

The first pass is truly aimed at providing a flyer that could be tacked up next to someone’s monitor.

http://www.scribd.com/doc/111473318/So-I-want-to-share-a-document-smartly%E2%80%A6

I made a second version, which I posted to Google Docs, so that the links are clickable as well.

If you’d like to examine the assumptions and reasoning behind this, I have a flowchart that I felt… might not be as helpful in conveying these ideas.

For what it’s worth, when I was discussing the response to this broadsheet (broadside?) among the folks at school, my wife asked me if I sent this as an attachment.

She’s a funny one.

October 29th, 2012

Posted In: How To

Tags: , , , , , , , , , , , , , , , , ,

For the last few years (my JSON feed tells me: since 2008), I have been tagging and annotating articles of interest as they passed before my eyes in Google Reader. This served a two-fold purpose:

  1. I could find them again later, easily, because they were tagged and annotated.
  2. I could share an RSS feed with those annotations to particular interest groups that I worked with (e.g. anything tagged “for robotics” would show up on my advanced computer science class’ portal page, or anything tagged “for academic computing” would show up on my school home page).

This was a great way to share (and manage) resources. Granted, much of what passed before my eyes in Google Reader was trivial and not of lasting value, but this filtering allowed me to hang on to at least a few gems for future reference.

And then Google Reader got the Google+ treatment and sharing items broke. But you could download a JSON dump of all the items that you had ever shared. It wasn’t entirely clear what you could do with this JSON dump, but… there it was. And then: I realized that all of my other information is stashed on my web server (and that I have become increasingly distrustful of relying on cloud services to maintain my data and workflows — e.g. my weekly backup of all my Google Docs… just in case).

Wouldn’t it be handy to import that JSON feed into a new blog on my server? So I wrote a PHP script that converts (at least my) Google Reader JSON dump into an XML file that WordPress can import as a list of posts. With the tags and annotations converted over. In fact, with all of the data in the JSON dump embedded in the XML file (although WordPress doesn’t read all of it).

This comes with a few caveats:

  • For items that came from blogs with a full feed, the result is a republication of the original post — which feels ethically dubious to me. (I have made my new blog of Google Reader shared items private, so that I have the data but I’m not sharing it with the world).
  • I’ve made guesses as to how to treat some of Google’s data. Reasoned, educated guesses, but guesses nonetheless. For example, I’m not super-clear on which dates in the file correspond with what events — does a publication date refer to when the item was shared or the original post was posted?
  • I’ve added in some arbitrary (and therefore, ideally, eventually, configurable) WordPress tags to make the import go more smoothly. Where I have done that, I mark it in the script as a TODO item. (And, in truth, I didn’t really test to see if all of these items were necessary.)
  • The original authors of the posts are transfered to the XML file, which means that when the actual import into WordPress is done, you will have the option to either laboriously create a new user for each distinct author or simply revert authorship to the currently logged-in WordPress user. It doesn’t seem like WordPress has a format for exporting or importing users (or, at least, my cursory search didn’t find it). Clearly an ancillary SQL query could be generated that pre-populated the WordPress database with the users that the XML file refers to. But I haven’t bothered to do that.
  • You’ll need your own PHP-compatible webserver to run the script, since I have been quick and dirty and simply imported the JSON file from and exported the XML file to the script’s local directory. And I have no interest in setting up my world-facing webserver to take the traffic hit of processing other people’s multi-megabyte JSON dumps.
With that said, here is the script, as it stands this morning.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
/**********************************************************************
 * Google Reader to Wordpress
 *
 * 2011-11-27
 * Seth Battis (seth@battis.net)
 * 
 * This script takes the output of Google Reader's JSON export of
 * shared items and converts it into an XML file that can be imported
 * into a Wordpress blog as posts. All of the data in the original JSON
 * file is preserved in the XML file, either by transfering it to
 * an appropriate format (e.g. Google Reader categories are converted
 * to WordPress post tags) or simply as an additional XML tag (e.g. the
 * Google Reader commentInfo metadata for recent shared items). In
 * situations where actual data has to be converted to make it readable
 * for WordPress, the original data is included as the JSON attribute
 * of that tag (e.g. timestamps and categories).
 *
 * As currently written, the script looks in its local directory for
 * Google Reader "shared-items.json" file and generates a matching
 * "shared-items.xml" file, also in its local directory.
 *
 * There are a number of potentially configurable (i.e. arbitrary)
 * values marked as TODO.
 *
 * Caveat emptor: this has been tested against my ~1000 item Google
 * Reader shared items feed and on my WordPress 3.2.1 site. I would
 * presume that it should work fairly well for others, but make no
 * guarantees!
 *********************************************************************/
 
/* returns a Wordpress slug-version of the given text (only
   alphanumeric characters and dashes) */
function sluggify($text)
{
	return preg_replace("|[^a-z0-9]+|", "-", strtolower($text));
}
 
/* SimpleXML doesn't really support namespaces unless you force it */
$xml = '<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0"
	xmlns:excerpt="http://wordpress.org/export/1.1/excerpt/"
	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:wp="http://wordpress.org/export/1.1/"
></rss>';
$rss = new SimpleXMLElement($xml);
$namespaces = $rss->getDocNamespaces(true);
 
/* load the Google Reader JSON file */
$file = file_get_contents("shared-items.json");
$json = json_decode($file, true);
 
/* Wordpress will choke if our post names aren't unique, so we track
   them separately */
$post_names = array();
 
/* header information describing the file itself */
$channel = $rss->addChild("channel");
$channel->addAttribute("direction", $json["direction"]);
$channel->addAttribute("id", $json["id"]);
$channel->addAttribute("self", $json["self"][0]["href"]);
$channel->addAttribute("author", $json["author"]);
$channel->addChild("title", $json["title"]);
$channel->addChild("link");
$channel->addChild("description");
$pubDate = $channel->addChild("pubDate", gmdate("D, j M Y G:i:s O", $json["updated"]));
$pubDate->addAttribute("json", $json["updated"]);
$channel->addChild("language");
$channel->addChild("wxr_version", "1.1", $namespaces["wp"]);
 
/* run through the list of items and add them to the XML */
foreach ($json["items"] as $item)
{
	$rssItem = $channel->addChild("item");
 
	/* a bunch of Google Reader-specific metadata */
	if (isset($item["isReadStateLocked"]))
	{
		$rssItem->addAttribute("isReadStateLocked", $item["isReadStateLocked"]);
	}
	$rssItem->addAttribute("crawlTimeMsec", $item["crawlTimeMsec"]);
	$rssItem->addAttribute("timestampUsec", $item["timestampUsec"]);
	$rssItem->addAttribute("id", $item["id"]);
	if (isset($item["commentInfo"]))
	{
		while (list($commentInfoKey, $commentInfoValue) = each($item["commentInfo"]))
		{
			$commentInfo = $rssItem->addChild("commentInfo", $commentInfoKey);
			$commentInfo->addAttribute("permalinkUrl", $commentInfoValue["permalinkUrl"]);
			$commentInfo->addAttribute("commentState", $commentInfoValue["commentState"]);
		}
	}
 
	/* annoyingly, not every item has its content in the content
	   element -- sometimes it's in the summary element (and once in a
	   while, it's just not there). I think this is an artifact of how
	   the original RSS feeds were constructed. I think. */
	if (isset($item["content"]))
	{
		$content = $item["content"]["content"];
	}
	else if (isset($item["summary"]["content"]))
	{
		$content = $item["summary"]["content"];
	}
	else
	{
		$content = "";
	}
 
	/* sometimes items don't even have titles */
	if (isset($item["title"]))
	{
		$rssItem->addChild("title", $item["title"]);
	}
 
	/* most items store the original linkback information in the
	   alternate element -- Wordpress won't honor this link tag when
	   it's imported (i.e. it won' treat it like the Daring Fireball
	   feed), so I have embedded a more descriptive linkback after the
	   annotations at the start of the content, using some information
	   from the origin element. The linkback is in the
	   "google-reader-alternate-href" div (for easy CSS-wrangling!) */
	if (isset($item["alternate"]))
	{
		$rssItem->addChild("link", htmlentities($item["alternate"][0]["href"], ENT_COMPAT, "UTF-8"));
		$content = htmlspecialchars("<div class=\"google-reader-alternate-href\"><p>Originally posted at <a href=\"{$item["alternate"][0]["href"]}\">{$item["origin"]["title"]}</a></p></div>", ENT_COMPAT, "UTF-8") . $content;
	}
 
	/* I haven't bothered to figure out if the dates are really GMT or
	   localized -- GMT was an easier assumption to make */
	$pubDate = $rssItem->addChild("pubDate", gmdate("D, j M Y G:i:s O", $item["published"]));
	$pubDate->addAttribute("json", $item["published"]);
 
	/* not every item has an a author, either -- again, an artifact of
	   the original RSS feeds */
	if (isset($item["author"]))
	{
		$rssItem->addChild("creator", $item["author"], $namespaces["dc"]);
	}
 
	/* annotations were tricky -- I have added them as their own XML
	   tags _and_ inserted them within a "google-reader-annotation" div
	   at the top of the post content (to match the original format on-
	   screen). All of the original data is preserved in the XML tag,
	   with an ID that matches the embedded div ID. */
	foreach($item["annotations"] as $annotation)
	{
		$annotationHTML = htmlentities("<div id=\"" . md5($annotation["content"] . $annotation["author"]) . "\" class=\"google-reader-annotation\"><blockquote><p>{$annotation["content"]}</p><p class=\"author\">{$annotation["author"]}</p></blockquote></div>", ENT_COMPAT, "UTF-8");
		$content = $annotationHTML . $content;
		$rssAnnotation = $rssItem->addChild("annotation", $annotation["content"]);
		$rssAnnotation->addAttribute("id", md5($annotation["content"] . $annotation["author"]));
		$rssAnnotation->addAttribute("author", $annotation["author"]);
		$rssAnnotation->addAttribute("userId", $annotation["userId"]);
		$rssAnnotation->addAttribute("profileId", $annotation["profileId"]);
		$rssAnnotation->addAttribute("profileCardParams", $annotation["profileCardParams"]);
	}
 
	/* again, sometimes content is in content, sometimes it's in the
	   summary element */
	$rssContent = $rssItem->addChild("encoded", $content, $namespaces["content"]);
	if (isset($item["content"]))
	{
		$rssContent->addAttribute("direction", $item["content"]["direction"]);
	}
	if (isset($item["summary"]))
	{
		$excerpt = $rssItem->addChild("encoded", $item["summary"]["content"], $namespaces["excerpt"]);
		$excerpt->addAttribute("direction", $item["summary"]["direction"]);
	}
 
	/* more Google reader metadata, this time about the original feed
	   that the item came from -- which is used above to format the
	   linkback that is embedded a the start of the content */
	$origin = $rssItem->addChild("origin");
	$origin->addAttribute("streamId", $item["origin"]["streamId"]);
	$origin->addAttribute("title", $item["origin"]["title"]);
	$origin->addAttribute("htmlUrl", $item["origin"]["htmlUrl"]);
 
	/* it's not clear to me whether the published or modified date is
	   when the original post was published or when the item <span class="hiddenGrammarError" pre="item ">was
	   shared</span> -- I think when published refers to when it was shared. */
	$postDate = $rssItem->addChild("post_date", date("Y-m-d G:i:s", $item["published"]), $namespaces["wp"]);
	$postDate->addAttribute("json", $item["published"]);
	$rssItem->addChild("comment_status", "open", $namespaces["wp"]);	// TODO make configurable
	$rssItem->addChild("ping_status", "open", $namespaces["wp"]);		// TODO make configurable
 
	/* make a Wordpress friendly title slug for the post */
	if (isset($item["title"]))
	{
		$slug = sluggify($item["title"]);
	}
	else
	{
		/* if no title, generate the slug from the timestamp */
		$slug = date("Y-m-d-G-i-s", $item["published"]);
	}
 
	/* make sure that our slug  is unique -- add a counter to the end
	   if it is not, and track those counter values in $post_names[] */
	if (isset($post_names[$slug]))
	{
		$post_names[$slug]++;
		$slug .= "-" . $post_names[$slug];
	}
	else
	{
		$post_names[$slug] = 0;
	}
	$rssItem->addChild("post_name", $slug, $namespaces["wp"]);
 
	/* more Wordpress metadata -- all of which could be tweaked */
	$rssItem->addChild("status", "publish", $namespaces["wp"]);	// TODO make configurable
	$rssItem->addchild("post_parent", 0, $namespaces["wp"]);	// TODO make configurable
	$rssItem->addChild("menu_order", 0, $namespaces["wp"]);		// TODO make configurable
	$rssItem->addChild("post_type", "post", $namespaces["wp"]);	// TODO make configurable
	$rssItem->addChild("post_password", "", $namespaces["wp"]);	// TODO make configurable
	$rssItem->addChild("is_sticky", 0, $namespaces["wp"]);		// TODO make configurable
 
	/* convert categories to post tags -- nota bene that Google Reader
	   has conflated the reader's categories with the original post's
	   tags, creating a... mish-mash. */
	foreach($item["categories"] as $category)
	{
		if (!preg_match("|.*/com\.google/.*|", $category))
		{
			$cleanCategory = $category;
			$cleanCategory = preg_replace("|user/\d+/label/(.*)|", "$1", $cleanCategory);
			$rssCategory = $rssItem->addChild("category", htmlentities($cleanCategory, ENT_COMPAT, "UTF-8"));
			$rssCategory->addAttribute("domain", "post_tag");
			$rssCategory->addAttribute("nicename", sluggify($cleanCategory));
			$rssCategory->addAttribute("json", $category);
		}
	}
 
	/* add comments -- note that for privacy reasons, while the
	   commenter's metadata is added as an XML tag, it is not embedded
	   in the Wordpress-readable wp:comment tags */
	foreach($item["comments"] as $comment)
	{
		$rssComment = $rssItem->addChild("comment", "", $namespaces["wp"]);
		$rssComment->addAttribute("id", $comment["id"]);
		$commentContent = $rssComment->addChild("comment_content", $comment["htmlContent"], $namespaces["wp"]);
		$commentContent->addAttribute("plainContent", $comment["plainContent"]);
		$author = $rssComment->addChild("comment_author", $comment["author"], $namespaces["wp"]);
		$author->addAttribute("userId", $comment["userId"]);
		$author->addAttribute("profileId", $comment["profileId"]);
		$author->addAttribute("profileCardParams", $comment["profileCardParams"]);
		$author->addAttribute("venueStreamid", $comment["venueStreamId"]);
		$commentDate = $rssComment->AddChild("comment_date", $comment["createdTime"], $namespaces["wp"]);
		$commentDate->addAttribute("modifiedTime", $comment["modifiedTime"]);
		$rssComment->addAttribute("isSpam", $comment["isSpam"]);
	}
}
 
/* dump the converted XML out as a file */
header ("Content-type: text/xml");
echo $rss->asXML();
file_put_contents("shared-items.xml", $rss->asXML());

November 27th, 2011

Posted In: How To, Social Bookmarking, Social Media, Useful Tools

Tags: , , , , , , ,

As noted earlier, there is a slick trick for taking a publicly accessible calendar in FirstClass and generating an iCalendar feed. Also noted earlier, the big problem with this feed is that it doesn’t contain timezone information, which makes some calendar systems (most notably Google Calendar) assume that everything is happening at Greenwich Mean Time. Which it usually isn’t. And I have written a PHP script that adds Pacific Timezone information to the iCalendar feed.

Let’s put all this together and take a current FirstClass calendar, make it readable from the web, feed it through the script and then add the result to your calendar program of choice.

  1. Right-click (or control-click, on a Mac) on the calendar in question and Add to Desktop. A second calendar icon will appear, possibly named with the name of whoever’s calendar it is. Possibly not. FirstClass is a mystery.
  2. Drag the new calendar into your Web Publishing folder (on some versions, Web Publishing may be called Home Page Folder — why is this? FirstClass is a mystery.)
  3. At this point you’re faced with a choice: either blithely disregard security, rely on security through obscurity, or be ready to generate a somewhat more aggravating URL to be more (but still not fully) secure.
    1. Disregard security: leave the calendar named whatever it’s currently named. You need to change the permissions (right-click/control-click and choose Permissions) so that All Users has Schedule+Details permissions on the calendar. This will change permissions for not just the copy in the Web Publishing folder, but also for the original calendar — since the “copy” in Web Publishing is just an alias to the original anyway.
    2. Security through obscurity: rename the calendar something else (I usually do this, and use a password generating application to give me a random collection of letters and numbers — e.g. a2612GhxU). Change permissions as described in 3(a) above.
    3. Better security: follow the directions here for generating your URL. Don’t tinker with permissions.
  4. Point a web browser at calendar in your Web Publishing folder, add the iCalendar feed get parameters, and copy that new URL to the clipboard.
  5. Point your web browser your copy of the time zone script and paste the URL you just copied into the Calendar URL field and click Generate.
  6. Copy the new URL that appears below. You can paste that URL into whatever calendaring system you want (that can subscribe to iCalendar feeds).
    1. In Google Calendar, you would Add a new calendar by URL and paste in your URL. (caveat: Google doesn’t seem to be too fantastic about actually updating iCalendar feeds — they allege that this is a sporadic issue, but I have experienced it as more prevalent than sporadic).
    2. In iCal, you would choose Subscribe… from the Calendar menu and paste in your URL.
    3. You could also paste this URL (via some contortions — e.g. email it to yourself and copy-paste from that) into a phone calendar app.

June 14th, 2011

Posted In: How To

Tags: , , , , , ,

For the last couple of months, one of my high school classes at Jewish Day School has been working on building an interactive tool about the Six Day War for a middle school curriculum unit. They have put a lot of work into researching their data and laying it out in Google Earth, and now we’re putting together a tour of the data for the middle school students, that will also teach them a little about how to use Google Earth. It’s been enormously fun.

But. But, we now have to do some programming or some very careful recording in Google Earth to create this tour (a là Al Gore’s Climate Change tours). And my students are far more interested in the design end of things than in the coding end of things. They’re great at what they do, but niggling coding details give them head aches.

So I built a Google Docs spreadsheet, into which they can plug various pertinent information and out of which comes valid KML instructions that will define the tour. I’m a little proud of this — I don’t think that there’s anything else quite like it out there. There are three kinds of information that need to be entered:

  1. SoundCue — any audio that should be played in the background. You can either enter the whole URL to the MP3 file that should be played, or just the file name (and make sure that you enter the path in which all the files can be found on the KML Generator tab). You also need to tell us both when the cue should be played and how long it should last.
  2. AnimatedUpdate — anything that should be happening in Google Earth in terms of showing photo overlays or placemarks or polygons or what-have-you. Enter what should happen, and when (and, if relevant, for how long).
  3. FlyTo — any time we change where (or when) we’re looking at Google Earth. Enter when and how long the transition should take, and the date (and time) that we’re meant to be looking at.

The AnimatedUpdate and the FlyTo also expect the user to enter KML code — which can be copy-and-pasted from Google Earth KML exports for each transition. Happily, if something needs to happen more than once, entering the KML for the first instance will automatically populate future instances. In addition, the pauses necessary to synch up all of the action are calculated by the spreadsheet.

Update March 25, 2011: The spreadsheet above is our live work (since I kept updating the spreadsheet after this post).

The end result is both a visual timeline of the tour (helpful for debugging any weird errors) and also KML code that can be copy-and-pasted out of Google Docs and into a KML file. (Caveat emptor: depending on what you’re pasting the KML into — I like Xmplify — you may see that there is a leading and trailing quote that need to be manually deleted.)

Right now the spreadsheet can handle tours up to four minutes and 10 seconds in length (250 seconds, for those of you keeping score at home). This is because I was originally copying the KML out of the KML Orderer worksheet, and Google Docs supports pasting of up to 1000 rows in total. You’ll see that the current KML worksheet is a single cell, getting around this limitation, but I didn’t bother to extend any of the other worksheets. Just make sure you fill down all the formulas if you extend any of the sheets!

Here’s a link to a scratch copy of the spreadsheet. Feel free to copy and use it for your own purposes — let me know how you used it!

February 12th, 2011

Posted In: Educational Technology, How To, Social Media, Useful Tools

Tags: , , , , , ,

I came across Rahul Mahtani and Yofred Moik’s conceptual design of a Google Mail Envelope a few days ago and was… instantly captivated. I’m not sure if it’s just the aesthetics of a design on the envelope in general, the way it hearkens back to an old school airmail envelope, or the conceptual neatness of the route between the two addresses. I just know that I love it and I want it.

So, I spent some time making a version of it.

Right now, my implementation is very much hacked together (I was teaching myself the Google Maps API as I went — it’s not hard, but it’s not familiar vocabulary, yet — I have a few other projects that will get me more expert soon, I hope). The things to know are:

  • Change the addresses and the map will (should) update to reflect the new information.
  • The first line of the address is removed on the assumption that it’s a name and not part of the address (and users are cruelly constrained to 3-line addresses right now).
  • The resulting envelope template is pretty much exactly a full-bleed letter-size page. Which means that printing it is a hassle.
  • I strongly suspect that there should be a dampening-down of the colors on the map so that the USPS can automatically scan the right information easily. My recollection from constructing bulk mailings a few years back is that the address just needs to have a bit of white space around it, but having a mess of other geographic information scattered nearby may not be helpful…
  • The snazzy orientation of the address infoWindows on the original design hasn’t happened yet. I think I have an idea of how to do it with some CSS (they won’t be “real” infoWindows), but haven’t taken the time to fiddle with it yet.
  • There’s something hinky with the borders of the side-flaps due to the not-yet-standard border-radius CSS.

More to come as way opens.

April 11th, 2010

Posted In: Computer Science

Tags: , , , , , , ,

This post is part of a series that are components of my “Expert Plan” at my school, looking to create a shared resource for my colleagues as the school moves towards greater adoption of laptops and technology in our pedagogy.

The Model

I wanted to structure the feedback that my students presented to each other during our video critique. We often brainstorm the criteria that we will be using to review work as it is presented, and I post our criteria on the board or on the wiki as a reminder throughout the process. This time, I created a new Form in Google Docs and entered our criteria as questions. I then embedded the form in our class notes for the day, and each student filled out the form as we viewed video. I embedded the responses as a spreadsheet on a linked page, so that the the students could review the feedback they had received and post their responses to our class blog.

In Practice

Creating the form live went relatively smoothly — the only hang-up was my inability to type in public. Fortunately, the students were proofreading on the screen and caught me when I made errors. They were also able to help guide me when I got distracted and forgot what I was doing (“Mr. Battis, we’ve already got that question at the bottom of the screen…”).

It took very little instruction for the students to figure out how to use the form. The most complicated thing they had to do was refresh the page after they had submitted their feedback so that they could get a fresh, blank form for the next video.

We settled into a routine where I played each video through twice, once for them to watch, and once to remind them of details as they entered their feedback.

Reflection

After we finished reviewing videos and posting feedback, I put the question to the students: was this better, worse or the same as having a verbal critique of the same material (which we had done in the earlier digital photography unit). The response, by and large, appeared to be that this was actually really helpful: there were enough things to pay attention to while watching the video that being able to take notes into the form let them not forget things that were important.

November 22nd, 2009

Posted In: "Expert Plan", Collaborative Writing and Editing, Educational Technology, Teaching

Tags: , , , , , , , , , , ,

This post is part of a series that are components of my “Expert Plan” at my school, looking to create a shared resource for my colleagues as the school moves towards greater adoption of laptops and technology in our pedagogy.

The Model

This started out as simply a way of addressing my own personal predilections: I’m tired of trying to build a syllabus on a calendar (the calendar doesn’t present the syllabus in a useful view), or in a spreadsheet (changing dates becomes a chore), so I decided to separate the various issues out and automate as much as I could. The result is a Google Docs spreadsheet that I embed on my course web site, which allows me to think in terms of units and lessons, and which automatically sequences lessons on to class meetings and updates me with notes about those specific days (Moadim L’Simcha, vacation days, etc.).

This is an example of one of my syllabi as the students see it:

In Practice

This turned out to be some pro-level spreadsheet work. I like working in spreadsheets. Not everyone likes working in spreadsheets. This link takes you to a “scratch” version of one of my syllabi (you’re welcome to edit it to see changes — this isn’t live. The organization is thus:

  • There is a worksheet for each unit of the syllabus, named sequentially Unit 1, Unit 2, Unit 3, etc. (The best way to create a new unit is to duplicate an old one and replace the information).
  • On a unit worksheet, a few of the columns are automatically filled in. You just have to worry about editing the title of the lesson, the lesson description, and the assignment summary. Everything else is filled in automatically.
  • The integrated view of all the units, sequenced together and lined up to days with notes is the Syllabus worksheet.
  • The Meetings worksheet is just a list of days when the class meets (which I entered manually) and any notes about that day specifically that might be helpful for lesson planning.
  • There are a bunch of “working” sheets that you can look at, but don’t edit — they’re collating and organizing all of the units automatically.

Reflection

This was way more work than it was worth for a single syllabus. But as a tool that I intend to reuse again and again, I’m pretty happy with it and feel good about the investment. It is mildly idiosyncratic, in the sense that it meets my specific needs. But it could be used as a model for other people’s style of syllabus design, separating the schedule from the concepts in a way that makes visualizing the lesson flow much, much easier.

November 22nd, 2009

Posted In: "Expert Plan", Educational Technology, How To, Teaching, Useful Tools

Tags: , , , , , , , , ,

My first year teaching, a bright young thing just out of college, I spent the summer before-hand in a state of panic: I assumed that, having gotten a job teaching AP Computer Science, that I would now need to be an infallible expert in computer science. This level of pressure had me practically hyperventilating before my first class.

Fortunately, what gradually became apparent to me (and has been reenforced often since, even as recently as my most recent evaluation this fall) is that the value in my teaching (and, I presume, all teaching) is not in what I can explain to the students, but what I can help them explain to me. And things get really exciting when my students get to explain things to me that are new to me. In fact, the most freeing moment I have had in the classroom was the first time I said, “Gee, that’s a good question. I have no idea. Let’s figure it out.” The practice of working as a collaborative team to solve a shared problem is real, and it is true learning.

Previously, I alluded to the idea of being willing to get lost in the hinterlands on the way to our educational destination. In this scenario, the teacher serves as the knowledgeable and resourceful guide. My best service to my students is to help them prepare for that journey, to load them with the background knowledge they need to tackle real and challenging problems. In this, the question of how and what to communicate to my students arises. I don’t want to tell them too much, for fear that they will come to rely on me, rather than their own intellect, for answers. But I certainly don’t want to tell them too little, for fear that they will never emerge from the hinterland.

This fall, as my computer science class can attest, we have swung both ways, but I think we’re finding a happy medium. As we reach equilibrium in that state of my life, I am turning my attention more directly to the other part of my life, working with faculty on uses and goals for academic computing. And I find myself in a similar bind. What and how should I be communicating? In a previous incarnation of this position, at another school, I believe I said too much and limited the creativity and actual learning of my colleagues. At the same time, I cannot rely on my colleagues now, who are working the so-called “triple threat” while striving to consistently improve their own teaching, to just “figure things out” on their own.

I think that this is exactly the situation for which we must prepare our students: we want our students to be able to lead, but to be able to collaborate with their colleagues to achieve the best possible results. How then, do we prepare our students to do this? And can we use these same tools ourselves to accomplish these same goals for ourselves (surely we should be as good at this as we would like our students to be, and if we’re not… now is an excellent time to get better!)

I believe the first step is to actually consider the nature of the communication that we are doing and to try to use appropriate tools for the problems at hand. Traditionally we are used to face-to-face meetings (which occur in real and simultaneous time for all participants, in a single location), mail (snail or electronic, it amounts to an asynchronous discussion occurring in multiple locations), or some telephonic communication (simultaneous real-time discussions occuring in two different locations). These same basic limitations apply to our communication with even the latest technology (are we in the same location or different locations? Can we talk at the same time or different times? Will this conversation take place all at once or over the course of several communications?)

But we have new tools that let us make better use of both our synchronous and asynchronous time. Consider the process of developing a joint document (a proposal for a new course to be taught by a team of teachers, for example). With our triple-threat schedule, we may only be able to find one quick time for a face-to-face meeting, but we can use wikis and other shared document tools (e.g. Google Docs) to share a single, evolving draft of our writing. Technologies are coming into play to allow us to do the same for video and audio editting.

Similarly, if we want to work the kinks out of a new idea, we would normally try for a face-to-face meeting (with a whiteboard, of course). But what if we blogged the idea, and then the discussion takes place in the comment threads? The same conversation can now take place asynchronously. Or perhaps we want to thread several discussion topics together, as in a discussion board, allowing for more free-wheeling discourse. Or we would like to link together connected ideas in different threads with hyperlink references.

What this describes is a new paradigm for communication. Processes which are traditionally thought of as happening synchronously and face-to-face can now be done asynchronously and at a distance. And this is what we need to be preparing our students to do. And the tools that we can use as teachers to work together in an increasingly pressured educational environment to squeeze the greatest result out of our efforts.

This does not supplant our traditional communication approaches, which still have great strengths (tone, inflection and body language, anyone) but complements them, allowing us to collaborate in a broader array of challenging situations to get more done with greater coordination of effort and less coordination of schedule.

January 2nd, 2008

Posted In: Educational Technology

Tags: , , , , , , , , , , , , ,

css.php