<?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>dougv.com « Doug Vanderweide &#187; Google</title>
	<atom:link href="http://www.dougv.com/tag/google/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.dougv.com</link>
	<description>ASP.NET, PHP, XML, JavaScript, Web geekery, Entrepreneurship</description>
	<lastBuildDate>Thu, 17 May 2012 22:33:05 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.2</generator>
		<item>
		<title>How To Increment A Counter In MySQL Based On A Radio Button Click</title>
		<link>https://www.dougv.com/2012/04/06/how-to-increment-a-counter-in-mysql-based-on-a-radio-button-click/</link>
		<comments>https://www.dougv.com/2012/04/06/how-to-increment-a-counter-in-mysql-based-on-a-radio-button-click/#comments</comments>
		<pubDate>Sat, 07 Apr 2012 00:42:06 +0000</pubDate>
		<dc:creator>Doug Vanderweide</dc:creator>
				<category><![CDATA[Databases]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[jQuery]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[arrays]]></category>
		<category><![CDATA[global constants]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[radio buttons]]></category>

		<guid isPermaLink="false">https://www.dougv.com/?p=4635</guid>
		<description><![CDATA[Asked recently on Formspring: how to increment count in database on clicking radio button There are a few ways to go about this. I&#8217;ll demonstrate two: a traditional, PHP / MySQL only, postback approach, and a jQuery version that uses AJAX to asynchronously record and update the counts. Just to be clear: In order to [...]<div class="yarpp">
	<h5>Related Posts</h5>
		<ol>
				<li><a href="https://www.dougv.com/2008/12/09/a-simple-page-click-count-system-using-php-and-mysql/" rel="bookmark">A Simple Page Click Count System Using PHP And MySQL</a> (22.3)</li>
				<li><a href="https://www.dougv.com/2006/11/26/a-simple-php-script-mysql-too-to-track-radio-station-song-requests-part-4/" rel="bookmark">A Simple PHP Script (MySQL, Too) To Track Radio Station Song Requests, Part 4</a> (20.4)</li>
				<li><a href="https://www.dougv.com/2006/11/26/a-simple-php-script-mysql-too-to-track-radio-station-song-requests-part-3/" rel="bookmark">A Simple PHP Script (MySQL, Too) To Track Radio Station Song Requests, Part 3</a> (20.1)</li>
			</ol>
	<p class="note">The numbers inside parentheses are relevance scores. Scoring is based, in order of priority, on title, category, content and tags. The higher the score, the more likely that post relates to this post.
	</div>
]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.formspring.me/dougvdotcom/q/312751702639910317" target="_blank">Asked recently on Formspring</a>:</p>
<blockquote><p>how to increment count in database on clicking radio button</p></blockquote>
<p>There are a few ways to go about this. I&#8217;ll demonstrate two: a traditional, PHP / MySQL only, postback approach, and a <a href="http://jquery.com/" target="_blank">jQuery</a> version that uses AJAX to asynchronously record and update the counts.</p>
<p>Just to be clear: In order to complete this solution, we have to use both JavaScript and a server-side scripting language. We use JavaScript to intercept the user clicking the radio button, but process the fact that the button was clicked on the server.</p>
<p>Also, for the purpose of this tutorial, I&#8217;ll assume that the radio button involved is part of a group. That is, we have several radio buttons, all with the same name, but different values, e.g.:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;form id=&quot;myform&quot; name=&quot;myform&quot; method=&quot;post&quot;&gt;
	&lt;p&gt;Select a color:&lt;/p&gt;
	&lt;label id=&quot;l_red&quot;&gt;&lt;input type=&quot;radio&quot; id=&quot;r_red&quot; name=&quot;color_name&quot; value=&quot;red&quot; /&gt;Red&lt;/label&gt; (&lt;label id=&quot;c_red&quot;&gt;0&lt;/label&gt;) |
	&lt;label id=&quot;l_green&quot;&gt;&lt;input type=&quot;radio&quot; id=&quot;r_green&quot; name=&quot;color_name&quot; value=&quot;green&quot; /&gt;Green&lt;/label&gt; (&lt;label id=&quot;c_green&quot;&gt;0&lt;/label&gt;) |
	&lt;label id=&quot;l_blue&quot;&gt;&lt;input type=&quot;radio&quot; id=&quot;r_blue&quot; name=&quot;color_name&quot; value=&quot;blue&quot; /&gt;Blue&lt;/label&gt; (&lt;label id=&quot;c_blue&quot;&gt;0&lt;/label&gt;) |
	&lt;label id=&quot;l_black&quot;&gt;&lt;input type=&quot;radio&quot; id=&quot;r_black&quot; name=&quot;color_name&quot; value=&quot;black&quot; /&gt;Black&lt;/label&gt; (&lt;label id=&quot;c_black&quot;&gt;0&lt;/label&gt;)
&lt;/form&gt;
</pre>
<p><span id="more-4635"></span></p>
<h3>Example 1: PHP / MySQL Postback</h3>
<p>The easiest way to process a click is right on the page that contains the radio button, via a simple <a href="http://en.wikipedia.org/wiki/Postback" target="_blank">postback</a>.</p>
<p>We begin with a MySQL table that will hold our count. It has two columns: color_name, which will contain unique values and thus can act as our table&#8217;s primary key; and color_count, the number of times that color has been clicked.</p>
<pre class="brush: sql; title: ; notranslate">
CREATE TABLE IF NOT EXISTS `colorcounter` (
  `colorname` varchar(5) NOT NULL,
  `colorcount` int(10) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`colorname`)
)
</pre>
<p>Next, we query the database for the current counts, which we&#8217;ll display in our form, later. We&#8217;ll dump the results set into an associative array, which will make outputting the counts in our form a little easier.</p>
<pre class="brush: php; title: ; notranslate">
$link = mysql_connect('server', 'user', 'password') or die('Cannot connect to database server');
mysql_select_db('database') or die('Cannot select database');

// click-count increment routine will go here; more on that shortly ...

// get current color click counts
$rs = mysql_query(&quot;SELECT * FROM colorcounter&quot;) or die ('Cannot process SQL count totals query');
if(mysql_num_rows($rs) &gt; 0) {
	while($row = mysql_fetch_array($rs)) {
		$count[$row['colorname']] = $row['colorcount'];
	}
}
</pre>
<p>We now need to set up our form to be ready to process the user click, and to display the click count records we previously received.</p>
<ul>
<li>We set the form to submit to itself.</li>
<li>We add an onclick event to each radio button, instructing it to submit the form. (The logic that will process the click follows shortly; we&#8217;re just assuming that every time the form is submitted, we need to increment a counter.)</li>
<li>We display the current click counts after each radio button.</li>
</ul>
<pre class="brush: xml; title: ; notranslate">
&lt;form id=&quot;myform&quot; name=&quot;myform&quot; method=&quot;post&quot; action=&quot;&lt;?php echo htmlspecialchars($_SERVER['PHP_SELF']); ?&gt;&quot;&gt;
	&lt;p&gt;Select a color:&lt;/p&gt;
	&lt;label id=&quot;l_red&quot;&gt;&lt;input type=&quot;radio&quot; id=&quot;r_red&quot; name=&quot;color_name&quot; value=&quot;red&quot; onclick=&quot;this.form.submit();&quot; /&gt;Red&lt;/label&gt; (&lt;label id=&quot;c_red&quot;&gt;&lt;?php echo $count['red']; ?&gt;&lt;/label&gt;) |
	&lt;label id=&quot;l_green&quot;&gt;&lt;input type=&quot;radio&quot; id=&quot;r_green&quot; name=&quot;color_name&quot; value=&quot;green&quot; onclick=&quot;this.form.submit();&quot; /&gt;Green&lt;/label&gt; (&lt;label id=&quot;c_green&quot;&gt;&lt;?php echo $count['green']; ?&gt;&lt;/label&gt;) |
	&lt;label id=&quot;l_blue&quot;&gt;&lt;input type=&quot;radio&quot; id=&quot;r_blue&quot; name=&quot;color_name&quot; value=&quot;blue&quot; onclick=&quot;this.form.submit();&quot; /&gt;Blue&lt;/label&gt; (&lt;label id=&quot;c_blue&quot;&gt;&lt;?php echo $count['blue']; ?&gt;&lt;/label&gt;) |
	&lt;label id=&quot;l_black&quot;&gt;&lt;input type=&quot;radio&quot; id=&quot;r_black&quot; name=&quot;color_name&quot; value=&quot;black&quot; onclick=&quot;this.form.submit();&quot; /&gt;Black&lt;/label&gt; (&lt;label id=&quot;c_black&quot;&gt;&lt;?php echo $count['black']; ?&gt;&lt;/label&gt;)
&lt;/form&gt;
</pre>
<p>Finally, we need to add some code to the page that will see if the form has been submitted, and increment the appropriate count.</p>
<pre class="brush: php; title: ; notranslate">
// if this is a postback ...
if(isset($_POST['color_name'])) {
	// create array of acceptable values
	$ok = array('red', 'green', 'blue', 'black');
	// if we have an acceptable value for color_name ...
	if(in_array($_POST['colorname'], $ok)) {
		// update the counter for that color
		$q = mysql_query(&quot;UPDATE colorcounter SET colorcount = colorcount + 1 WHERE colorname = '&quot; . $_POST['color_name'] . &quot;'&quot;) or die (&quot;Error updating count for &quot; . $_POST['color_name']);
	}
}
</pre>
<p>Note that this code block should appear <em>before</em> the code block that gets the current click counts (where I included the comment in the first block of PHP code), in order to ensure the counts are up-to-the-minute.</p>
<p>You can see a working demo here: <a href="http://www.dougv.com/demo/ajax_radio_clickcount/index.php" target="_blank">http://www.dougv.com/demo/ajax_radio_clickcount/index.php</a></p>
<h3>Example 2: Using AJAX</h3>
<p>The previous example has a number of drawbacks, not the least of them being that each time you click on a radio button, the entire form posts back to the server. </p>
<p>That limits its usefulness quite a bit. We could work around this problem a few ways; perhaps making sure we <a href="https://www.dougv.com/2009/06/13/retaining-values-in-a-form-following-php-postback-and-clearing-form-values-after-successful-php-form-processing/" title="Retaining Values In A Form Following PHP Postback And Clearing Form Values After Successful PHP Form Processing">repopulate our form fields following the postback</a>, and only performing final processing when the submit button is clicked (by adding an onclick event to that button, which hands off the results to a JavaScript function or a different processing page).</p>
<p>That&#8217;s an awful lot of work for very little benefit. It makes way more sense to leverage AJAX, and update our click counts without having to post the form itself back to the server.</p>
<p>JavaScript &#8212; more specifically, jQuery &#8212; to the rescue!</p>
<div class="aside">I could have written a traditional AJAX solution here. However, it&#8217;s wordy to do that in a way that ensures browser comparability. </p>
<p>To me, it makes sense to encumber the jQuery library whenever you create an AJAX script, rather than reinventing the wheel with an old-school <a href="http://www.w3schools.com/xml/xml_http.asp" target="_blank">XMLHTTPRequest</a> object that has to be written several ways to ensure it works in whatever browser you encounter.</div>
<p>To get our jQuery solution to work, we need to create two PHP &#8220;helper&#8221; pages: one that will update our click counts whenever a radio button is selected, and the other that will get the current click counts, both on initial page load and following a click.</p>
<h3>The get_counts.php Helper Page</h3>
<p>Let&#8217;s start by calling the helper page that gets our click counts get_counts.php. It works almost exactly like the code block that gets the counts in our PHP postback example, except this page is going to output our results in <a href="http://www.json.org/" target="_blank">JSON</a>.</p>
<p>We&#8217;re putting the results out that way because it&#8217;s very easy for jQuery to process data in JSON format. </p>
<div class="aside"><strong>A crash course in JSON:</strong> In the end, JSON is just a fancy way to create associative arrays in JavaScript. Or, more simply, it&#8217;s an easy way to supply a whole bunch of key-value pairs to a JavaScript function.</p>
<p>Or, to put it even another way, think of JSON as the JavaScript version of an XML file. It&#8217;s just a way to organize data into columns and rows.</p>
<p>JSON is actually a bit more complex than that, but the way we&#8217;re using it, JSON is just a recordset, exactly like we would get from a MySQL query. It&#8217;s just written differently.</div>
<p>Let&#8217;s look  page will expect to see a querystring variable named &#8220;color,&#8221; which indicates which value needs to be incremented. The page code itself will be very similar to the processing code in our postback version.</p>
<pre class="brush: php; title: ; notranslate">
$link = mysql_connect('server', 'user', 'password') or die('Cannot connect to database server');
mysql_select_db('database') or die('Cannot select database');

// get new count totals, pass as JSON
$rs = mysql_query(&quot;SELECT * FROM colorcounter&quot;) or die('Cannot get updated click counts');
if(mysql_num_rows($rs) &gt; 0) {
	$out = &quot;{ &quot;;
	while($row = mysql_fetch_array($rs)) {
		$out .= &quot;\&quot;$row[colorname]\&quot; : $row[colorcount], &quot;;
	}
	$out = substr($out, 0, strlen($out) - 2);
	$out .= &quot; }&quot;;

	header(&quot;Content-type: application/json&quot;);
	echo $out;
}
</pre>
<h3>The increment_counter.php Helper Page</h3>
<p>Now that we have a way to get the counts, we need a way to increment them. Once again, we make a PHP helper page &#8212; increment_counter.php &#8212; that works fundamentally the same as the code block that increments the click counts in our PHP postback page, except that it calls upon GET, instead of POST. </p>
<p>(Yes, you can use <a href="http://api.jquery.com/jQuery.ajax/" target="_blank">jQuery to make POST and GET AJAX requests</a>. When I&#8217;m sending a single variable that just needs to meet a range, I prefer to use GET.)</p>
<pre class="brush: php; title: ; notranslate">
$link = mysql_connect('server', 'user', 'password') or die('Cannot connect to database server');
mysql_select_db('database') or die('Cannot select database');

// if this is a postback ...
if(isset($_GET['color'])) {
	// create array of acceptable values
	$ok = array('red', 'green', 'blue', 'black');
	// if we have an acceptable value for color_name ...
	if(in_array($_GET['color'], $ok)) {
		// update the counter for that color
		$q = mysql_query(&quot;UPDATE colorcounter SET colorcount = colorcount + 1 WHERE colorname = '&quot; . $_GET['color'] . &quot;'&quot;) or die (&quot;Error updating count for &quot; . $_GET['color']);
	}
}
</pre>
<h3>The Form And JavaScript</h3>
<p>With our helper pages out of the way, we can proceed to work on the form and the jQuery JavaScript needed to make this work.</p>
<p>As usual, our first step is to procure and include a copy of jQuery on our page. Lately, I prefer to simply link to the version hosted by the <a href="https://developers.google.com/speed/libraries/devguide" target="_blank">Google Libraries API</a>, but you can <a href="http://docs.jquery.com/Downloading_jQuery" target="_blank">download jQuery directly</a> and run it off your server if you prefer. Either way, call it somewhere in your page&#8217;s head section:</p>
<pre class="brush: xml; title: ; notranslate">&lt;script type=&quot;text/javascript&quot; src=&quot;https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js&quot;&gt;&lt;/script&gt;</pre>
<p>Next, I want to strip the form back to its basic components. </p>
<pre class="brush: xml; title: ; notranslate">
&lt;form id=&quot;myform&quot; name=&quot;myform&quot; method=&quot;post&quot;&gt;
	&lt;p&gt;Select a color:&lt;/p&gt;
	&lt;label id=&quot;l_red&quot;&gt;&lt;input type=&quot;radio&quot; id=&quot;r_red&quot; name=&quot;color_name&quot; value=&quot;red&quot; /&gt;Red&lt;/label&gt; (&lt;label id=&quot;c_red&quot;&gt;0&lt;/label&gt;) |
	&lt;label id=&quot;l_green&quot;&gt;&lt;input type=&quot;radio&quot; id=&quot;r_green&quot; name=&quot;color_name&quot; value=&quot;green&quot; /&gt;Green&lt;/label&gt; (&lt;label id=&quot;c_green&quot;&gt;0&lt;/label&gt;) |
	&lt;label id=&quot;l_blue&quot;&gt;&lt;input type=&quot;radio&quot; id=&quot;r_blue&quot; name=&quot;color_name&quot; value=&quot;blue&quot; /&gt;Blue&lt;/label&gt; (&lt;label id=&quot;c_blue&quot;&gt;0&lt;/label&gt;) |
	&lt;label id=&quot;l_black&quot;&gt;&lt;input type=&quot;radio&quot; id=&quot;r_black&quot; name=&quot;color_name&quot; value=&quot;black&quot; /&gt;Black&lt;/label&gt; (&lt;label id=&quot;c_black&quot;&gt;0&lt;/label&gt;)
&lt;/form&gt;
</pre>
<p>Notice that I no longer have onclick events assigned to each radio button. That&#8217;s because I&#8217;ll wire up that event via <a href="http://api.jquery.com/ready/" target="_blank">$(document).ready</a>.</p>
<p>More on that shortly. First, I want to declare a <a href="https://www.dougv.com/2006/12/18/variable-scope-made-simple/" title="Variable Scope Made Simple" target="_blank">global variable</a>, lastChecked. It will keep track of the value of the last radio button that was checked. </p>
<p>This will keep our page from recording multiple clicks on the same radio button, over and over again; clicks won&#8217;t be recorded unless, and until, a new value is clicked. In other words, if I just keep clicking Red, it will only be recorded once; if I then click Blue, that will be incremented, and clicking Red again will increment its value, once again.</p>
<pre class="brush: jscript; title: ; notranslate">var lastClicked = '';</pre>
<p>I next want to write JavaScript functions for each of my helper pages. The first, getTotals(), will &#8212; as its name suggests &#8212; send an AJAX request to get the current click totals. It will then assign those totals to the labels after each radio button.</p>
<pre class="brush: jscript; title: ; notranslate">
function getTotals() {
	// function to get click counts as JSON from helper page
	// expects get_count.php to be in same directory level

	$.ajax({
		type: &quot;GET&quot;,
		url: &quot;get_count.php&quot;,
		dataType: &quot;json&quot;,
		error: function(xhr, status, msg) {
			alert(&quot;Failed to get click counts: &quot; + msg);
		}
	})
	.done(function(data) {
		// loop through JSON variables, assign to count labels
		$.each(data, function(key, value) {
			var tmp = &quot;#c_&quot; + key;
			$(tmp).text(value);
		});
	});
}
</pre>
<p>I can now write the JavaScript function that will increment click counts. It&#8217;s named, quite cleverly, processClick, and takes as its argument the radio button (obj, for &#8220;object&#8221;) that was clicked.</p>
<pre class="brush: jscript; title: ; notranslate">

function processClick(obj) {
	// function to increment click count via ajax
	// expects increment_count.php to be in same directory level

	if(lastClicked != obj.val()) { // don't count clicks on currently active radio button
		lastClicked = obj.val(); // set currently clicked radio button to this one

		var qs = &quot;color=&quot; + obj.val(); // set query string value

		$.ajax({
			type: &quot;GET&quot;,
			url: &quot;increment_count.php&quot;,
			data: qs,
			error: function(xhr, status, msg) {
				alert(&quot;Failed to process click count: &quot; + msg);
			}
		})
		.done(function() {
			getTotals(); // update totals on successful processing
		});
	}
}
</pre>
<p>Finally, we want to use $(document).ready to initially populate the click total labels, and to assign processClick as the event handler for clicks on all the radio buttons on the page.</p>
<pre class="brush: jscript; title: ; notranslate">

$(document).ready(function() {
	getTotals(); // get click totals on initial page load

	$(document).ready(function() {
		// add click incrementing event handler to all radio buttons on page
		$('input:radio').click(function() {
			processClick($(this));
		});
	});
});
</pre>
<div class="aside">No doubt some of you have noticed that I pass the entire radio button object to my processClick function, when all I really need to pass is its value. I see it as six of one, half-dozen of another, but if there&#8217;s a compelling argument against sending the entire object vs. its value, leave a comment and let&#8217;s discuss it. I&#8217;m always happy to clean up bad code.</div>
<p>You can see a working example here: <a href="http://www.dougv.com/demo/ajax_radio_clickcount/example2.htm" target="_blank">http://www.dougv.com/demo/ajax_radio_clickcount/example2.htm</a></p>
<h3>Notes And Code</h3>
<p>A few notes about this project:</p>
<p>It&#8217;s not overly difficult for someone to click spam what I have here, either by simply clicking a radio button multiple times, or by calling the increment_count.php helper page multiple times.</p>
<p>A way around that would be to create single-use tokens for each iteration of the form, so that only one click per rendering of the form would be allowed.</p>
<p>You could do that by creating a session variable or cookie, if you&#8217;re using the PHP postback example; setting a Boolean in that session / cookie to be true the first time the form is submitted; and refusing to process the click if that session / cookie Boolean is true.</p>
<p>Or, for more security, you could add another MySQL table, which counts the number of times your form has been rendered. It would consist of an autoincremented primary key, which would act as an identifier for this instance of the form; and an tinyint column for recording a Boolean, which would indicate if the form had been submitted. </p>
<p>Every time your form is rendered, you would insert a new record in your form-rendering table, with a Boolean value of false indicating that the form has not yet been processed, then use mysql_insert_id to get the primary key value of the record you just created.</p>
<p>Using <a href="http://php.net/manual/en/book.mcrypt.php" target="_blank">mcrypt</a>, you would then create a single-use token by encrypting that record ID with a secret key only you know, and pass that to whatever method you are using to increment the counter. </p>
<p>That processing code would decrypt the token, make sure the record with that ID has a Boolean false as its &#8220;form has been submitted&#8221; value. If not, the code would not process the click; but if so, the code would process the click, then mark the form&#8217;s Boolean value as true.</p>
<p>An added benefit of this method is that it also inhibits forgeries and cross-site scripting attacks, since the only way to create legitimate tokens is via your secret key. (Theoretically, a hacker could crack your key, but use a sufficient passphrase and a good crypto algorithm, and the chances of anyone even taking a crack at it, nonetheless succeeding, are infinitesimal.)</p>
<p>Sounds more complex than it is. I&#8217;ve got demonstrating how to make a single-use token like this in Evernote blog column ideas. If significant interest is expressed, I&#8217;ll move it to the top of the list.</p>
<p>You can download the code in this post here: <a href='https://www.dougv.com/wp-content/uploads/2012/04/ajax_radio_clickcount.zip'>https://www.dougv.com/wp-content/uploads/2012/04/ajax_radio_clickcount.zip</a></p>
<p>All links in this post on delicious: <a href="http://delicious.com/dougvdotcom/how-to-increment-a-counter-in-mysql-based-on-a-radio-button-click" target="_blank">http://delicious.com/dougvdotcom/how-to-increment-a-counter-in-mysql-based-on-a-radio-button-click</a></p>
<div class="yarpp">
	<h5>Related Posts</h5>
		<ol>
				<li><a href="https://www.dougv.com/2008/12/09/a-simple-page-click-count-system-using-php-and-mysql/" rel="bookmark">A Simple Page Click Count System Using PHP And MySQL</a> (22.3)</li>
				<li><a href="https://www.dougv.com/2006/11/26/a-simple-php-script-mysql-too-to-track-radio-station-song-requests-part-4/" rel="bookmark">A Simple PHP Script (MySQL, Too) To Track Radio Station Song Requests, Part 4</a> (20.4)</li>
				<li><a href="https://www.dougv.com/2006/11/26/a-simple-php-script-mysql-too-to-track-radio-station-song-requests-part-3/" rel="bookmark">A Simple PHP Script (MySQL, Too) To Track Radio Station Song Requests, Part 3</a> (20.1)</li>
			</ol>
	<p class="note">The numbers inside parentheses are relevance scores. Scoring is based, in order of priority, on title, category, content and tags. The higher the score, the more likely that post relates to this post.</p>
	</div>

	Tags: <a href="https://www.dougv.com/tag/arrays/" title="arrays" rel="tag">arrays</a>, <a href="https://www.dougv.com/tag/global-constants/" title="global constants" rel="tag">global constants</a>, <a href="https://www.dougv.com/tag/google/" title="Google" rel="tag">Google</a>, <a href="https://www.dougv.com/tag/radio-buttons/" title="radio buttons" rel="tag">radio buttons</a><br />
]]></content:encoded>
			<wfw:commentRss>https://www.dougv.com/2012/04/06/how-to-increment-a-counter-in-mysql-based-on-a-radio-button-click/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>Displaying Selected YouTube Data API Thumbnails On A Web Page Via ASP.NET Web Forms</title>
		<link>https://www.dougv.com/2012/03/15/displaying-selected-youtube-data-api-thumbnails-on-a-web-page-via-asp-net-web-forms/</link>
		<comments>https://www.dougv.com/2012/03/15/displaying-selected-youtube-data-api-thumbnails-on-a-web-page-via-asp-net-web-forms/#comments</comments>
		<pubDate>Thu, 15 Mar 2012 23:37:41 +0000</pubDate>
		<dc:creator>Doug Vanderweide</dc:creator>
				<category><![CDATA[API]]></category>
		<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Social Media]]></category>
		<category><![CDATA[Web Forms]]></category>
		<category><![CDATA[YouTube]]></category>
		<category><![CDATA[YouTube Data API]]></category>
		<category><![CDATA[bit.ly]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[Microsoft]]></category>
		<category><![CDATA[REST]]></category>
		<category><![CDATA[RSS]]></category>
		<category><![CDATA[XPath]]></category>
		<category><![CDATA[XSLT]]></category>

		<guid isPermaLink="false">http://www.dougv.com/?p=4325</guid>
		<description><![CDATA[Describes how to query the YouTube Data API, process its response XML, and display thumbnails hyperlinked to youtube.com video pages.<div class="yarpp">
	<h5>Related Posts</h5>
		<ol>
				<li><a href="https://www.dougv.com/2012/03/08/displaying-selected-youtube-video-thumbnails-on-an-asp-net-web-forms-page/" rel="bookmark">Displaying Selected YouTube Video Thumbnails On An ASP.NET Web Forms Page</a> (64.7)</li>
				<li><a href="https://www.dougv.com/2011/04/24/automatically-hash-tagging-text-with-asp-net-web-forms-vb-net/" rel="bookmark">Automatically Hash Tagging Text With ASP.NET Web Forms (VB.NET)</a> (24.5)</li>
				<li><a href="https://www.dougv.com/2011/12/25/parent-child-dropdownlist-controls-in-asp-net-web-forms-vb-net/" rel="bookmark">Parent-Child DropDownList Controls In ASP.NET Web Forms (VB.NET)</a> (23.9)</li>
			</ol>
	<p class="note">The numbers inside parentheses are relevance scores. Scoring is based, in order of priority, on title, category, content and tags. The higher the score, the more likely that post relates to this post.
	</div>
]]></description>
			<content:encoded><![CDATA[<p>Previously, I blogged about &#8220;<a href="http://www.dougv.com/2012/03/08/displaying-selected-youtube-video-thumbnails-on-an-asp-net-web-forms-page/" title="Displaying Selected YouTube Video Thumbnails On An ASP.NET Web Forms Page">Displaying Selected YouTube Video Thumbnails On An ASP.NET Web Forms Page</a>,&#8221; when you know the video IDs of the thumbnails you want to hyperlink.</p>
<p>A reader recently asked me how to hyperlink YouTube video thumbnails based on searching for a keyword. I promised to address that, so here goes.</p>
<p>Interestingly enough, searching the <a href="https://developers.google.com/youtube/" target="_blank">YouTube Data API</a> is accomplished in a <a href="http://www.infoq.com/articles/rest-introduction" target="_blank">REST</a>-like manner quite similar to the methodology I used for <a href="http://www.dougv.com/2009/07/02/shortening-urls-with-the-bit-ly-api-via-asp-net/" title="Shortening URLs With The bit.ly API Via ASP.NET">shortening URLs in ASP.NET via the bit.ly API</a>. </p>
<ul>
<li>Form a simple request URL to the YouTube Data API that contains the appropriate search parameters;</li>
<li>Use a <a href="http://msdn.microsoft.com/en-us/library/system.net.webrequest.aspx" target="_blank">WebRequest</a> to send that URL to Google, which returns an XML document with results;</li>
<li>Use <a href="http://msdn.microsoft.com/en-us/library/system.net.webresponse.aspx" target="_blank">WebResponse</a> to dump that stream into an <a href="http://msdn.microsoft.com/en-us/library/system.xml.xmldocument.aspx" target="_blank">XmlDocument</a>;</li>
<li>Use <a href="http://www.w3schools.com/xpath/default.asp" target="_blank">XPath</a> and <a href="http://msdn.microsoft.com/en-us/library/system.xml.xmlnode.aspx" target="_blank">XmlNode</a>&#8216;s SelectNodes method to recursively get the thumbnails from each entry; and</li>
<li>Bind up a pile of <a href="http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.hyperlink.aspx" target="_blank">Hyperlink</a> controls, which are added dynamically to a <a href="http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.panel.aspx" target="_blank">Panel</a> control.</li>
</ul>
<p>Sounds more complicated than it actually is. Let&#8217;s do it.<br />
<span id="more-4325"></span></p>
<h3>Overview Of The YouTube Data API</h3>
<p>First step in leveraging the YouTube Data API: <a href="https://code.google.com/apis/youtube/dashboard" target="_blank">Get a Youtube Data API developer key</a>.</p>
<div class="aside">As of this writing, you can keyword search query the YouTube Data API without supplying a developer key and get a useful response. However, Google wants you to supply a key, and a time may come where you must supply one. So take the 30 seconds required to register a key.</div>
<p>With our developer key in hand, we can proceed to build a simple keyword query. In this case, we&#8217;re going to get the top 12 videos that meet the search criteria &#8220;Miss Maine.&#8221; </p>
<p>For our purposes, &#8220;top 12 videos&#8221; means the 12 videos YouTube deems most relevant to the exact search term &#8220;Miss Maine.&#8221; In other words, our search is going to work exactly as though we had typed &#8220;Miss Maine&#8221; (including the double quotes) into the search box at youtube.com; the results we get back should be the same as the first 12 results we would get via a default search on youtube.com. (For the usual vague reasons, this won&#8217;t always be the case, but the results sets will be similar.)</p>
<p>The YouTube Data API tells us that, <a href="https://developers.google.com/youtube/2.0/reference#Searching_for_videos" target="_blank">to do a search query, we can GET a request to its servers</a>, with our search parameters as querystring variables, and Google will return to us an XML document containing records that match our query.</p>
<p>Because we&#8217;re not requesting user-specific data, we don&#8217;t need to authenticate our requests; as previously mentioned, we don&#8217;t even need to send along our developer key. (But, again, being courteous and thorough, we will send it, as requested.)</p>
<p>So, the methodology we use to get our records is exactly the same as we used to shorten a URL via the bit.ly API; we&#8217;re going to create a URL containing all our parameters, then make a WebRequest to the YouTube Data API; we&#8217;ll get its returned XML via a WebResponse, and push that into an XmlDocument, from which we can extract the information we want. </p>
<div class="aside">As we build this solution, let&#8217;s keep some non-technical things in mind:</p>
<ul>
<li>Many of the videos on YouTube aren&#8217;t very good, in the aesthetic, intellectual or civil senses. In other words, much of what is on YouTube is terrible.</li>
<li>A sizable number of YouTube videos are spam.</li>
<li>It&#8217;s <em>de rigueur</em> to keyword spam video descriptions, especially if the video is spam.</li>
<li>Trolling is mandatory for YouTube video comments, and you&#8217;ll probably be linking directly to that.</li>
</ul>
<p>We can mitigate the damage, somewhat, by applying the smartSearch filter, being very specific with our search term, including specific words we don&#8217;t want returned in our results, restricting the categories in which we want to search &#8230; in other words, being as specific as we can about what we want to see.</p>
<p>However, I&#8217;ll bet a dollar to doughnuts that if you request any sizable result set on any generic search term, your results set is going to contain items you wish weren&#8217;t in there. That&#8217;s just the nature of the thing, and there&#8217;s little that can be done about it.</p></div>
<h3>Querying The YouTube Data API</h3>
<p>There are three distinct phases in our solution: query the API, get the information we need from its response, and put that information on the page. Therefore, we will use three distinct functions and subroutines to do the work. </p>
<p>First up, a function to submit our query. It accepts as arguments our developer key and a formatted querystring, and it puts the response into an XML document for us. If the request fails, it will report as much in a Label control, and return an empty XML document.</p>
<pre class="brush: vb; title: ; notranslate">
Function QueryYouTubeDataAPI(ByVal strAPIKey As String, ByVal strQuery As String) As XmlDocument
	'Returns empty XMLDocument on failure, results XML on success
	'This function requires your page to have a label control named lblStatus for error reporting

	'build URL to shorten method resource
	Dim strUri As New StringBuilder(&quot;https://gdata.youtube.com/feeds/api/videos?&quot;)
	strUri.Append(&quot;v=2&quot;)
	strUri.Append(&quot;&amp;&quot;)
	strUri.Append(strQuery)
	strUri.Append(&quot;&amp;key=&quot;)
	strUri.Append(Server.HtmlEncode(strAPIKey))
	strUri.Append(&quot;&amp;prettyprint=true&quot;) 'adds line breaks &amp; white space to response; useful for debugging

	Dim objRequest As HttpWebRequest
	Dim objResponse As WebResponse
	Dim objXML As New XmlDocument() 'This is the document the function will return

	Try
		'create request for shorten resource
		objRequest = WebRequest.Create(strUri.ToString)
		'since we are passing querystring variables, our method is get
		objRequest.Method = &quot;GET&quot;
		'act as though we are sending a form
		objRequest.ContentType = &quot;application/x-www-form-urlencoded&quot;
		'don't wait for a 100 Continue HTTP response
		objRequest.ServicePoint.Expect100Continue = False
		'since we are using get, we need not send a request body; set content-length to 0
		objRequest.ContentLength = 0

		'read the Data API response into XML document
		objResponse = objRequest.GetResponse()
		objXML.Load(objResponse.GetResponseStream())
	Catch ex As Exception
		lblStatus.Text = &quot;Error querying YouTube Data API. Message: &quot; &amp; ex.Message
	End Try

	'send XML Document
	Return objXML
End Function
</pre>
<div class="aside"><strong>A note about error trapping:</strong> The error trapping I have used here does not check whether or not the YouTube Data API returns a sensible result. It only checks that we formed a request and the API responded.</p>
<p>If there is a problem with the query string you send to the YouTube Data API, or your request is somehow malformed, the API will return to you the 25 most popular videos for the day, as of the time of the request.</p>
<p>You can check to see if the YouTube Data API is seeing your request properly by looking for the /feed/link rel=&#8217;self&#8217; node:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;feed xmlns='http://www.w3.org/2005/Atom' xmlns:app='http://www.w3.org/2007/app' xmlns:media='http://search.yahoo.com/mrss/' xmlns:openSearch='http://a9.com/-/spec/opensearch/1.1/' xmlns:gd='http://schemas.google.com/g/2005' xmlns:yt='http://gdata.youtube.com/schemas/2007' gd:etag='W/&amp;quot;CkYNSH07cSp7I2A9WhVSGEg.&amp;quot;'&gt;
	&lt;!-- ... previous nodes --&gt;
	&lt;link rel='self' type='application/atom+xml' href='https://gdata.youtube.com/feeds/api/videos?q=%22Miss+Maine%22&amp;amp;start-index=1&amp;amp;max-results=12&amp;amp;duration=short&amp;amp;safeSearch=strict&amp;amp;orderby=relevance_lang_en&amp;amp;v=2'/&gt;
	&lt;!-- additional nodes ... --&gt;
&lt;/feed&gt;
</pre>
<p>If the URL in that node doesn&#8217;t resemble the one you sent, most likely your querystring is malformed; but possibly, there are other errors in your request URL.
</p></div>
<h3>Creating The Query String</h3>
<p>You&#8217;ll notice that I am not very specific about the query string argument passed to the previous function. That&#8217;s because I want to give you maximum flexibility, and the best way to do that is to allow for a free-form query string to be supplied. </p>
<p>So let&#8217;s look at how I&#8217;ll build my sample query string. I&#8217;ll do that, of course, with key-value pairs. That is, I will specify the parameter I want to send, and set it as equal to something.</p>
<p>First up, the search term. We&#8217;re going to use &#8220;Miss Maine.&#8221; So, the first part of our query string is:</p>
<pre class="brush: plain; title: ; notranslate">q=&quot;Miss Maine&quot;</pre>
<div class="aside">Note that the YouTube Data API says all parameters should be URL encoded. Our function will take care of that for us when we create the WebRequest, so don&#8217;t bother encoding your query string in advance, or you&#8217;ll get errors. Just pass it to the function as natural text.</div>
<p>Technically, we can stop here and let the default API request variables take over. But I want to be more specific about my search results, so let&#8217;s add some more parameters.</p>
<p>Next, I&#8217;ll limit my responses to 12 records, by appending that key-value pair to my string:</p>
<pre class="brush: plain; title: ; notranslate">q=&quot;Miss Maine&quot;&amp;max-results=12</pre>
<p>I&#8217;d like only short (under 4 minutes) videos:</p>
<pre class="brush: plain; title: ; notranslate">q=&quot;Miss Maine&quot;&amp;max-results=12&amp;duration=short</pre>
<p>To cull at least some offensive material, I&#8217;ll use strict safeSearch:</p>
<pre class="brush: plain; title: ; notranslate">q=&quot;Miss Maine&quot;&amp;max-results=12&amp;duration=short&amp;safeSearch=strict</pre>
<p>Finally, I&#8217;m going to specifically ask for English language videos that best match my search term. So my completed query string is:</p>
<pre class="brush: plain; title: ; notranslate">q=&quot;Miss Maine&quot;&amp;max-results=12&amp;duration=short&amp;safeSearch=strict&amp;orderby=relevance_lang_en</pre>
<div class="aside">You may be wondering why I am not using the <a href="http://code.google.com/p/google-gdata/" target="_blank">GData .NET Library</a> to accomplish this search. That&#8217;s an excellent option in general, but it involves installing components on your local machine and Web server. </p>
<p>For many Web developers, adding DLLs to a Web server or installing assemblies on a workstation can be problematic, if not prohibited outright. So I want to use as many built-in resources as possible. However, if you can install the GData API, consider going that route.</p></div>
<h3>Examining The XML Response</h3>
<p>Assuming our request goes through &#8212; that is, our Web server actually delivers the request to YouTube, which in turn sends a response back &#8212; we now have an XML document in storage that contains the information we requested. (Or, in the event of a malformed request, the default results set previously described.)</p>
<p>You can see the XML document returned for my demo query string here: <a href="http://www.dougv.net/demos/youtube_data_api_thumbs/videos.xml" target="_blank">http://www.dougv.net/demos/youtube_data_api_thumbs/videos.xml</a></p>
<p>In our case, we want some specific data for each video:</p>
<ul>
<li>Its title;</li>
<li>the default thumbnail; and</li>
<li>the URL to its player page on youtube.com.</li>
</ul>
<p>We get back lots more useful information in the XML response: The name of the uploader, the run time of the video in seconds, its likes and dislikes count, the categories and keywords assigned to the video, the date and time it was uploaded, its view count, etc. But for this solution, we&#8217;ll only use the video&#8217;s title, thumbnail and URL.</p>
<p>Although the actual XML document contains much more data per entry, for our purposes, the relevant structure of the document looks like this (I&#8217;ve stripped out extraneous attributes):</p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version='1.0' encoding='UTF-8'?&gt;
&lt;feed&gt;
	&lt;entry&gt;
		&lt;title&gt;&lt;/title&gt;
		&lt;link rel='alternate' /&gt;
		&lt;media:group&gt;
			&lt;media:thumbnail /&gt;
		&lt;/media:group&gt;
	&lt;/entry&gt;
&lt;/feed&gt;
</pre>
<p>In practice, the parts of an entry (that is, an individual record) that we want to use look like this:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;entry gd:etag='W/&amp;quot;DU8AQ347eCp7I2A9WhRWEEQ.&amp;quot;'&gt;
	&lt;title&gt;Miss Maine USA 2011&lt;/title&gt;
	&lt;link rel='alternate' type='text/html' href='https://www.youtube.com/watch?v=STm4GUqwLVo&amp;amp;feature=youtube_gdata'/&gt;
	&lt;media:group&gt;
		&lt;media:thumbnail url='http://i.ytimg.com/vi/STm4GUqwLVo/default.jpg' height='90' width='120' time='00:00:43.500' yt:name='default'/&gt;
	&lt;/media:group&gt;
&lt;/entry&gt;
</pre>
<h3>Traversing The XML Response Document</h3>
<div class="aside">There are a number of ways in ASP.NET to traverse / select nodes from an XML document. </p>
<p>The way most Web resources will tell you to do it is with LINQ to XML. LINQ allows us to make a SQL-like query of the XML document, and receive in return a SQL-like recordset, which we can then use to ouput our data.</p>
<p>LINQ to XML is definitely the way to go if you&#8217;re going to work with a large XML document, or need to extract data in a very complicated way (e.g, get various node attributes; select nodes based on data contained in other nodes). </p>
<p>LINQ to XML would work here, too, but I think it&#8217;s a bit of overkill, considering we only need three values from 12 records. So I&#8217;m just going to use the built-in parsing functions that are part of XmlDocument, a class we have already encumbered. Also, I&#8217;m more used to using XPath than I am LINQ.</p>
<p>Another way is to create an XML Stylesheet (<a href="http://www.w3schools.com/xsl/" target="_blank">XSLT</a>) which will &#8220;flattened out&#8221; the XML document into 12 nodes (each representing one record) with three attributes (each representing the title, URL and thumbnail for that record). I describe that methodology at &#8220;<a href="http://www.dougv.com/2006/11/11/using-national-weather-service-xml-feeds-with-aspnet-adonet-and-xsl/" title="Using National Weather Service XML Feeds With ASP.NET, ADO.NET And XSL" target="_blank">Using National Weather Service XML Feeds With ASP.NET, ADO.NET And XSL</a>.&#8221; </p>
<p>But I am trying to limit the number of things you have to learn at one time to accomplish this task. In my estimation, it&#8217;s a bit easier to hack together an XPath expression than to write an XSLT, so this is how I&#8217;m proceeding.</p></div>
<p>We&#8217;ll need three XPath expressions that will give us, for each of the 12 videos returned:</p>
<ol>
<li>the inner text of title,</li>
<li>the inner text of the link node that has the attribute &#8216;alternate&#8217;, and </li>
<li>the url attribute of whichever media:thumbnail node that has the additional attribute of yt:name=&#8217;default&#8217;.</li>
</ol>
<p>The XML returned by the YouTube Data API uses several namespaces (atom, media, yt, etc.). Therefore, we first have to make reference to those namespaces in order to traverse the response XML.</p>
<p>We do that by creating an XmlNamespaceManager, then adding the specification URLs to it:</p>
<pre class="brush: vb; title: ; notranslate">
'add namespaces so we can traverse this thing
Dim xmlNSM As New XmlNamespaceManager(xmlDoc.NameTable)
xmlNSM.AddNamespace(&quot;atom&quot;, &quot;http://www.w3.org/2005/Atom&quot;)
xmlNSM.AddNamespace(&quot;media&quot;, &quot;http://search.yahoo.com/mrss/&quot;)
xmlNSM.AddNamespace(&quot;yt&quot;, &quot;http://gdata.youtube.com/schemas/2007&quot;)
</pre>
<p>(We got these URLs from the root (&#8220;feed&#8221;) node&#8217;s attributes. That&#8217;s where they always appear, any time an XML document references namespace(s).</p>
<div class="aside">An <a href="http://www.w3.org/TR/REC-xml-names/" target="_blank">XML namespace</a> is basically a description of what elements an XML document will have, the kinds of data each node will contain, and other useful technical information that helps a parser understand what it is looking at and how to consume it.</div>
<p>Now that we have the namespaces referenced, we can go ahead and create our XPath arguments. Here they are:</p>
<pre class="brush: vb; title: ; notranslate">
'let's get our entry node values
Dim xmlTitleNodes As XmlNodeList = xmlDoc.SelectNodes(&quot;/atom:feed/atom:entry/atom:title&quot;, xmlNSM)
Dim xmlURLNodes As XmlNodeList = xmlDoc.SelectNodes(&quot;/atom:feed/atom:entry/media:group/media:player&quot;, xmlNSM)
Dim xmlThumbNodes As XmlNodeList = xmlDoc.SelectNodes(&quot;/atom:feed/atom:entry/media:group/media:thumbnail[@yt:name='default']&quot;, xmlNSM)
</pre>
<p>Note that I use the namespace:node syntax to select the nodes I want. If I didn&#8217;t do that, the XML parser wouldn&#8217;t understand what I meant if I said simply, &#8220;feed&#8221;, for example. I have to let the parser know which namespace the element &#8220;feed&#8221; belongs to, so it can find it.</p>
<p>With our nodes on hand, we just need to iterate each XmlNodeList, get our values, and pass them to a subroutine that adds hyperlinked thumbnails to our page.</p>
<p>We can use a simple For loop here, because by design, the number of title, thumbnail and hyperlink nodes will be the same, and the nodes will be in the same order in all three XmlNodeLists. (That is, the first node in each XmlNodeList will be for the first record; the title, URL and thumbnail values will match up, because they were listed in order in the XML document.)</p>
<pre class="brush: vb; title: ; notranslate">
'For loop will iterate them; by definition, counts are the same for each XmlNodeList
For I = 0 To xmlTitleNodes.Count - 1
	strTitle = xmlTitleNodes.Item(I).InnerText
	strURL = xmlURLNodes.Item(I).Attributes(&quot;url&quot;).Value
	strThumb = xmlThumbNodes.Item(I).Attributes(&quot;url&quot;).Value

	CreateHyperlinkedThumb(strTitle, strURL, strThumb)
Next
</pre>
<p>Here&#8217;s what the entire subroutine looks like:</p>
<pre class="brush: vb; title: ; notranslate">
Sub MakeThumbnailLinks(ByVal strAPIKey As String, ByVal strQuery As String)
	'create XML document we will parse
	Dim xmlDoc As New XmlDocument()
	xmlDoc = QueryYouTubeDataAPI(strAPIKey, strQuery)

	'add namespaces so we can traverse this thing
	Dim xmlNSM As New XmlNamespaceManager(xmlDoc.NameTable)
	xmlNSM.AddNamespace(&quot;atom&quot;, &quot;http://www.w3.org/2005/Atom&quot;)
	xmlNSM.AddNamespace(&quot;media&quot;, &quot;http://search.yahoo.com/mrss/&quot;)
	xmlNSM.AddNamespace(&quot;yt&quot;, &quot;http://gdata.youtube.com/schemas/2007&quot;)

	'let's get our entry node values
	Dim xmlTitleNodes As XmlNodeList = xmlDoc.SelectNodes(&quot;/atom:feed/atom:entry/atom:title&quot;, xmlNSM)
	Dim xmlURLNodes As XmlNodeList = xmlDoc.SelectNodes(&quot;/atom:feed/atom:entry/media:group/media:player&quot;, xmlNSM)
	Dim xmlThumbNodes As XmlNodeList = xmlDoc.SelectNodes(&quot;/atom:feed/atom:entry/media:group/media:thumbnail[@yt:name='default']&quot;, xmlNSM)

	'for debugging, we'll also get the url that represents what the Data API actually used
	Dim objNode As XmlNode = xmlDoc.SelectSingleNode(&quot;/atom:feed/atom:link[@rel='self']&quot;, xmlNSM)
	lblStatus.Text = &quot;&lt;strong&gt;Request URL returned by YouTube Data API:&lt;/strong&gt; &quot; &amp; Server.HtmlEncode(objNode.Attributes(&quot;href&quot;).Value)

	'strings to store values
	Dim strTitle As String
	Dim strURL As String
	Dim strThumb As String

	'looping variable
	Dim I As Integer

	'For loop will iterate them; by definition, counts are the same for each XmlNodeList
	For I = 0 To xmlTitleNodes.Count - 1
		strTitle = xmlTitleNodes.Item(I).InnerText
		strURL = xmlURLNodes.Item(I).Attributes(&quot;url&quot;).Value
		strThumb = xmlThumbNodes.Item(I).Attributes(&quot;url&quot;).Value

		CreateHyperlinkedThumb(strTitle, strURL, strThumb)
	Next
End Sub
</pre>
<h3>Creating Thumbs From Results</h3>
<p>The CreateHyperlinkedThumb subroutine takes the title, URL and thumbnail strings we got, applies them as properties to a Hyperlink control, and adds those controls to a Panel on our page.</p>
<pre class="brush: vb; title: ; notranslate">
Sub CreateHyperlinkedThumb(strTitle As String, strURL As String, strThumb As String)
	'create hyperlink
	Dim ctlLink As New HyperLink()

	'set values
	ctlLink.Text = strTitle
	ctlLink.ToolTip = strTitle
	ctlLink.NavigateUrl = strURL
	ctlLink.ImageUrl = strThumb
	ctlLink.CssClass = &quot;margin-5&quot;
	ctlLink.Target = &quot;video&quot;

	'add to panel
	pnlThumbs.Controls.Add(ctlLink)
End Sub
</pre>
<p>And with that, we&#8217;re done!</p>
<p>To invoke, we simply call the MakeThumbnailLinks subroutine, passing to it our YouTube Data API developer key and the querystring we built:</p>
<pre class="brush: vb; title: ; notranslate">MakeThumbnailLinks(&quot;YOUTUBE_DATA_API_DEVELOPER_KEY&quot;, &quot;q=&quot;&quot;Miss Maine&quot;&quot;&amp;max-results=12&amp;duration=short&amp;safeSearch=strict&amp;orderby=relevance_lang_en&quot;)</pre>
<p>You can see a working demo here: <a href="http://www.dougv.net/demos/youtube_data_api_thumbs/Example2.aspx" target="_blank">http://www.dougv.net/demos/youtube_data_api_thumbs/Example2.aspx</a></p>
<p>The code for this solution, as well as &#8220;<a href="http://www.dougv.com/2012/03/08/displaying-selected-youtube-video-thumbnails-on-an-asp-net-web-forms-page/" title="Displaying Selected YouTube Video Thumbnails On An ASP.NET Web Forms Page" target="_blank">Displaying Selected YouTube Video Thumbnails On An ASP.NET Web Forms Page</a>,&#8221; can be downloaded here: <a href='http://www.dougv.com/wp-content/uploads/2012/03/youtube_data_api_thumbs.zip'>http://www.dougv.com/wp-content/uploads/2012/03/youtube_data_api_thumbs.zip</a></p>
<p>All links in this post on delicious: <a href="http://delicious.com/dougvdotcom/displaying-selected-youtube-data-api-thumbnails-on-a-web-page-via-asp-net-web-forms" target="_blank">http://delicious.com/dougvdotcom/displaying-selected-youtube-data-api-thumbnails-on-a-web-page-via-asp-net-web-forms</a></p>
<div class="yarpp">
	<h5>Related Posts</h5>
		<ol>
				<li><a href="https://www.dougv.com/2012/03/08/displaying-selected-youtube-video-thumbnails-on-an-asp-net-web-forms-page/" rel="bookmark">Displaying Selected YouTube Video Thumbnails On An ASP.NET Web Forms Page</a> (64.7)</li>
				<li><a href="https://www.dougv.com/2011/04/24/automatically-hash-tagging-text-with-asp-net-web-forms-vb-net/" rel="bookmark">Automatically Hash Tagging Text With ASP.NET Web Forms (VB.NET)</a> (24.5)</li>
				<li><a href="https://www.dougv.com/2011/12/25/parent-child-dropdownlist-controls-in-asp-net-web-forms-vb-net/" rel="bookmark">Parent-Child DropDownList Controls In ASP.NET Web Forms (VB.NET)</a> (23.9)</li>
			</ol>
	<p class="note">The numbers inside parentheses are relevance scores. Scoring is based, in order of priority, on title, category, content and tags. The higher the score, the more likely that post relates to this post.</p>
	</div>

	Tags: <a href="https://www.dougv.com/tag/bit-ly/" title="bit.ly" rel="tag">bit.ly</a>, <a href="https://www.dougv.com/tag/google/" title="Google" rel="tag">Google</a>, <a href="https://www.dougv.com/tag/microsoft/" title="Microsoft" rel="tag">Microsoft</a>, <a href="https://www.dougv.com/tag/rest/" title="REST" rel="tag">REST</a>, <a href="https://www.dougv.com/tag/rss/" title="RSS" rel="tag">RSS</a>, <a href="https://www.dougv.com/tag/xpath/" title="XPath" rel="tag">XPath</a>, <a href="https://www.dougv.com/tag/xslt/" title="XSLT" rel="tag">XSLT</a><br />
]]></content:encoded>
			<wfw:commentRss>https://www.dougv.com/2012/03/15/displaying-selected-youtube-data-api-thumbnails-on-a-web-page-via-asp-net-web-forms/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>What&#8217;s Happening This Week On dougv.com</title>
		<link>https://www.dougv.com/2012/03/11/whats-happening-this-week-on-dougv-com/</link>
		<comments>https://www.dougv.com/2012/03/11/whats-happening-this-week-on-dougv-com/#comments</comments>
		<pubDate>Sun, 11 Mar 2012 23:10:29 +0000</pubDate>
		<dc:creator>Doug Vanderweide</dc:creator>
				<category><![CDATA[General]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[coding standards]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[Server Integration Method]]></category>

		<guid isPermaLink="false">http://www.dougv.com/?p=4463</guid>
		<description><![CDATA[An update on what&#8217;s coming to this blog the week of March 11-17, 2012: Part 3 of using the authorize.net Server Integration Method with PHP. This post will cover the process of checking form inputs for range and proper values; storing the order&#8217;s details locally, for fulfillment; and submitting the order for payment. Later, I&#8217;ll [...]<div class="yarpp">
	<h5>Related Posts</h5>
		
No related posts.
	</div>
]]></description>
			<content:encoded><![CDATA[<p>An update on what&#8217;s coming to this blog the week of March 11-17, 2012:</p>
<p><strong>Part 3 of using the authorize.net Server Integration Method with PHP.</strong> This post will cover the process of checking form inputs for range and proper values; storing the order&#8217;s details locally, for fulfillment; and submitting the order for payment. </p>
<p>Later, I&#8217;ll post on using Relay Response to finalize orders; as well as a simple system for seeing order details / cleaning up incomplete or abandoned orders.</p>
<p><strong>Part 2 of displaying YouTube thumbnails on an ASP.NET Web Forms page.</strong> Specifically, I&#8217;ll describe the process of searching for a term via the YouTube Data API, getting the XML result returned by Google, and displaying that information on your page.</p>
<p>Also this week, I am going through old blog entries, cleaning up their appearance, checking code and fixing the taxonomies (categories / tags) in which they belong.</p>
<p>The old syntax highlighting plugin I was using has left some code blocks unreadable, and some of the older code I have written is either superseded by a later post or isn&#8217;t really the best way to go about doing things. I&#8217;ll be noting that where it&#8217;s the case.</p>
<p>That process is proving slow; I am though about 50 posts so far, with about another 200 to go. So bear with me, and thanks!</p>
<div class="yarpp">
	<h5>Related Posts</h5>
		<p>No related posts.</p>
	</div>

	Tags: <a href="https://www.dougv.com/tag/blogging/" title="blogging" rel="tag">blogging</a>, <a href="https://www.dougv.com/tag/coding-standards/" title="coding standards" rel="tag">coding standards</a>, <a href="https://www.dougv.com/tag/google/" title="Google" rel="tag">Google</a>, <a href="https://www.dougv.com/tag/server-integration-method/" title="Server Integration Method" rel="tag">Server Integration Method</a><br />
]]></content:encoded>
			<wfw:commentRss>https://www.dougv.com/2012/03/11/whats-happening-this-week-on-dougv-com/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Displaying Selected YouTube Video Thumbnails On An ASP.NET Web Forms Page</title>
		<link>https://www.dougv.com/2012/03/08/displaying-selected-youtube-video-thumbnails-on-an-asp-net-web-forms-page/</link>
		<comments>https://www.dougv.com/2012/03/08/displaying-selected-youtube-video-thumbnails-on-an-asp-net-web-forms-page/#comments</comments>
		<pubDate>Thu, 08 Mar 2012 16:27:59 +0000</pubDate>
		<dc:creator>Doug Vanderweide</dc:creator>
				<category><![CDATA[API]]></category>
		<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Web Forms]]></category>
		<category><![CDATA[YouTube Data API]]></category>
		<category><![CDATA[arrays]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[hotlinking]]></category>
		<category><![CDATA[Microsoft]]></category>
		<category><![CDATA[Stack Overflow]]></category>

		<guid isPermaLink="false">http://www.dougv.com/?p=4251</guid>
		<description><![CDATA[Describes how to hyperlink YouTube video thumbnails on an ASP.NET Web Forms Page, linking to those videos on YouTube.com.<div class="yarpp">
	<h5>Related Posts</h5>
		<ol>
				<li><a href="https://www.dougv.com/2011/04/24/automatically-hash-tagging-text-with-asp-net-web-forms-vb-net/" rel="bookmark">Automatically Hash Tagging Text With ASP.NET Web Forms (VB.NET)</a> (24)</li>
				<li><a href="https://www.dougv.com/2011/12/25/parent-child-dropdownlist-controls-in-asp-net-web-forms-vb-net/" rel="bookmark">Parent-Child DropDownList Controls In ASP.NET Web Forms (VB.NET)</a> (23.8)</li>
				<li><a href="https://www.dougv.com/2008/08/21/displaying-an-image-stored-in-a-sql-server-database-on-an-aspnet-page-using-vbnet/" rel="bookmark">Displaying An Image Stored In A SQL Server Database On An ASP.NET Page Using VB.NET</a> (22.5)</li>
			</ol>
	<p class="note">The numbers inside parentheses are relevance scores. Scoring is based, in order of priority, on title, category, content and tags. The higher the score, the more likely that post relates to this post.
	</div>
]]></description>
			<content:encoded><![CDATA[<p>I received a kind email recently from a reader, thanking me for my article titled &#8220;<a href="http://www.dougv.com/2009/06/13/retaining-values-in-a-form-following-php-postback-and-clearing-form-values-after-successful-php-form-processing/" title="Retaining Values In A Form Following PHP Postback And Clearing Form Values After Successful PHP Form Processing">Retaining Values In A Form Following PHP Postback And Clearing Form Values After Successful PHP Form Processing</a>&#8220;. </p>
<p>He also asked how he could use the <a href="https://developers.google.com/youtube/2.0/developers_guide_protocol_audience" target="_blank">YouTube Data API</a> to search for videos by keyword; display thumbnails of those videos on an ASP.NET page; then either hyperlink to those videos on YouTube, or display them on his page via the <a href="https://developers.google.com/youtube/js_api_reference" target="_blank">YouTube Player API</a>.</p>
<p>I&#8217;ll address that specific question in an upcoming post. First, I want to show how to do what the questioner asks if you already know the video IDs of specific YouTube videos you want to show on a page.</p>
<p>If you know the video ID(s) for the YouTube videos you want to display on a page, you can call them directly from YouTube&#8217;s image servers, thanks to a predictable URL and naming scheme, and hotlinking.<br />
<span id="more-4251"></span><br />
As <a href="http://stackoverflow.com/questions/2068344/how-to-get-thumbnail-of-YouTube-video-link-using-YouTube-api" target="_blank">this thread at Stack Overflow</a> notes, every YouTube video has at least three thumbnails, numbered 1.jpg to 3.jpg. These images are 120px by 60px and are roughly taken at the start, middle, and end of the video, respectively.</p>
<p>The file named default.jpg is the default thumbnail at 120px x 90px resolution. Two others &#8212; 0.jpg and hqdefault.jpg &#8212; are 480px x 360px. All three of these default images are copies of whichever of the three video thumbnails has been set as the default frame capture for the video.</p>
<p>Finally, a maximum resolution version, available as maxresdefault.jpg, is available if there is a high-definition (720p or 1080p) version of the video. It measures 1280px x 720px. When there isn&#8217;t a high definition version of the video, this appears as the generic YouTube thumbnail icon, at 120px x 90px resolution.</p>
<p>So, here are all the thumbnail images available for <a href="http://www.YouTube.com/watch?v=TvwJMa5b1Qg" target="_blank">Lisa Hannigan&#8217;s &#8220;What&#8217;ll I Do&#8221; music video</a> (hover over each image to see its name):</p>
<p><img src="http://img.YouTube.com/vi/TvwJMa5b1Qg/default.jpg" alt="default.jpg" title="default.jpg" />&nbsp;<img src="http://img.YouTube.com/vi/TvwJMa5b1Qg/1.jpg" alt="1.jpg" title="1.jpg" />&nbsp;<img src="http://img.YouTube.com/vi/TvwJMa5b1Qg/2.jpg" alt="2.jpg" title="2.jpg" />&nbsp;<img src="http://img.YouTube.com/vi/TvwJMa5b1Qg/3.jpg" alt="3.jpg" title="3.jpg" /></p>
<p><img src="http://img.YouTube.com/vi/TvwJMa5b1Qg/0.jpg" alt="0.jpg" title="0.jpg" />  <img src="http://img.YouTube.com/vi/TvwJMa5b1Qg/hqdefault.jpg" alt="hqdefault.jpg" title="hqdefault.jpg" /></p>
<p><img src="http://img.YouTube.com/vi/TvwJMa5b1Qg/maxresdefault.jpg" alt="maxresdefault.jpg" title="maxresdefault.jpg" style="width:100%;" /> </p>
<p>And here are the thumbnails for <a href="http://www.YouTube.com/watch?v=VUUSdvwEC_Y" target="_blank">a decidedly low-res video for The Monkees&#8217;s song, &#8220;Last Train To Clarksville&#8221;</a>:</p>
<p><img src="http://img.YouTube.com/vi/VUUSdvwEC_Y/default.jpg" alt="default.jpg" title="default.jpg" />&nbsp;<img src="http://img.YouTube.com/vi/VUUSdvwEC_Y/1.jpg" alt="1.jpg" title="1.jpg" />&nbsp;<img src="http://img.YouTube.com/vi/VUUSdvwEC_Y/2.jpg" alt="2.jpg" title="2.jpg" />&nbsp;<img src="http://img.YouTube.com/vi/VUUSdvwEC_Y/3.jpg" alt="3.jpg" title="3.jpg" /></p>
<p><img src="http://img.YouTube.com/vi/VUUSdvwEC_Y/0.jpg" alt="0.jpg" title="0.jpg" /> <img src="http://img.YouTube.com/vi/VUUSdvwEC_Y/hqdefault.jpg" alt="hqdefault.jpg" title="hqdefault.jpg" /></p>
<p><img src="http://img.YouTube.com/vi/VUUSdvwEC_Y/maxresdefault.jpg" alt="maxresdefault.jpg" title="maxresdefault.jpg" /></p>
<h3>ASP.NET Web Forms Code To Display And Hyperlink Known YouTube Video Thumbnails</h3>
<p>Here&#8217;s some simple ASP.NET Web Forms code to add hyperlinked thumbnails for five different YouTube videos, the IDs of which we know, to a Panel control. (I&#8217;m assuming here that the Panel control is named pnlThumbs.)</p>
<pre class="brush: vb; title: ; notranslate">
Sub AddVideoThumbs()
	'create ArrayList of known video IDs
	Dim arrIDs As New ArrayList
	arrIDs.Add(&quot;STxXS5lLunE&quot;)
	arrIDs.Add(&quot;Mwrl6bWfvrc&quot;)
	arrIDs.Add(&quot;4nTo8rjo-lM&quot;)
	arrIDs.Add(&quot;eVVXtknZVf0&quot;)
	arrIDs.Add(&quot;pxg113O_SRI&quot;)

	'some temp string variables for our processing loop
	Dim strTemp As String
	Dim strLink As String
	Dim strSrc As String

	For Each strTemp In arrIDs
		strSrc = &quot;http://img.YouTube.com/vi&quot; &amp; strTemp &amp; &quot;/default.jpg&quot; 'path to default thumbnail
		strLink = &quot;http://www.YouTube.com/watch?v=&quot; &amp; strTemp 'path to video on YouTube

		Dim objLink As New HyperLink 'create hyperlink
		objLink.NavigateUrl = strLink
		objLink.ImageUrl = strSrc
		objLink.Text = strTemp 'sets alt text to ensure XHTML compliance
		objLink.Target = &quot;video&quot; 'will open all videos in same, new window / tab
		objLink.CssClass = &quot;margin-0-5&quot; 'a specific-to-me CSS class to space out the thumbnails

		pnlThumbs.Controls.Add(objLink)
	Next
End Sub
</pre>
<p>You can see this in action here: <a href="http://www.dougv.net/demos/YouTube_data_api_thumbs/Default.aspx" target="demo">http://www.dougv.net/demos/youtube_data_api_thumbs/Default.aspx</a></p>
<p>In an upcoming post, I will describe how to use the YouTube Data API to search for videos, display their thumbnails on a page, and hyperlink to those videos. I&#8217;ll also package all the demo code together for download at that time.</p>
<div class="aside">Update, 15 March 2012: The second part of this tutorial, and downloadable demo code for both examples, is available at <a href="http://www.dougv.com/2012/03/15/displaying-selected-youtube-data-api-thumbnails-on-a-web-page-via-asp-net-web-forms/" title="Displaying Selected YouTube Data API Thumbnails On A Web Page Via ASP.NET Web Forms">Displaying Selected YouTube Data API Thumbnails On A Web Page Via ASP.NET Web Forms</a>.&#8221;</div>
<p>All links in this post on delicious: <a href="http://delicious.com/dougvdotcom/displaying-selected-youtube-video-thumbnails-on-an-asp-net-web-forms-page" target="_blank">http://delicious.com/dougvdotcom/displaying-selected-youtube-video-thumbnails-on-an-asp-net-web-forms-page</a></p>
<div class="yarpp">
	<h5>Related Posts</h5>
		<ol>
				<li><a href="https://www.dougv.com/2011/04/24/automatically-hash-tagging-text-with-asp-net-web-forms-vb-net/" rel="bookmark">Automatically Hash Tagging Text With ASP.NET Web Forms (VB.NET)</a> (24)</li>
				<li><a href="https://www.dougv.com/2011/12/25/parent-child-dropdownlist-controls-in-asp-net-web-forms-vb-net/" rel="bookmark">Parent-Child DropDownList Controls In ASP.NET Web Forms (VB.NET)</a> (23.8)</li>
				<li><a href="https://www.dougv.com/2008/08/21/displaying-an-image-stored-in-a-sql-server-database-on-an-aspnet-page-using-vbnet/" rel="bookmark">Displaying An Image Stored In A SQL Server Database On An ASP.NET Page Using VB.NET</a> (22.5)</li>
			</ol>
	<p class="note">The numbers inside parentheses are relevance scores. Scoring is based, in order of priority, on title, category, content and tags. The higher the score, the more likely that post relates to this post.</p>
	</div>

	Tags: <a href="https://www.dougv.com/tag/arrays/" title="arrays" rel="tag">arrays</a>, <a href="https://www.dougv.com/tag/google/" title="Google" rel="tag">Google</a>, <a href="https://www.dougv.com/tag/hotlinking/" title="hotlinking" rel="tag">hotlinking</a>, <a href="https://www.dougv.com/tag/microsoft/" title="Microsoft" rel="tag">Microsoft</a>, <a href="https://www.dougv.com/tag/stack-overflow/" title="Stack Overflow" rel="tag">Stack Overflow</a><br />
]]></content:encoded>
			<wfw:commentRss>https://www.dougv.com/2012/03/08/displaying-selected-youtube-video-thumbnails-on-an-asp-net-web-forms-page/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Converting Latitude And Longitude Coordinates Between Decimal And Degrees, Minutes, Seconds</title>
		<link>https://www.dougv.com/2012/03/07/converting-latitude-and-longitude-coordinates-between-decimal-and-degrees-minutes-seconds/</link>
		<comments>https://www.dougv.com/2012/03/07/converting-latitude-and-longitude-coordinates-between-decimal-and-degrees-minutes-seconds/#comments</comments>
		<pubDate>Wed, 07 Mar 2012 23:18:18 +0000</pubDate>
		<dc:creator>Doug Vanderweide</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[converters]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[latitude / longitude]]></category>

		<guid isPermaLink="false">http://www.dougv.com/?p=4315</guid>
		<description><![CDATA[PHP functions that convert latitude and longitude coordinates between decimal and degrees - minutes - seconds (DMS).<div class="yarpp">
	<h5>Related Posts</h5>
		<ol>
				<li><a href="https://www.dougv.com/2009/07/13/calculating-the-bearing-and-compass-rose-direction-between-two-latitude-longitude-coordinates-in-php/" rel="bookmark">Calculating The Bearing And Compass Rose Direction Between Two Latitude / Longitude Coordinates In PHP</a> (43.3)</li>
				<li><a href="https://www.dougv.com/2006/12/20/displaying-a-random-yahoo-search-every-30-seconds-with-javascript-and-php/" rel="bookmark">Displaying A Random Yahoo! Search Every 30 Seconds With JavaScript And PHP</a> (15.4)</li>
				<li><a href="https://www.dougv.com/2009/03/27/getting-all-zip-codes-in-a-given-radius-from-a-known-point-zip-code-via-php-and-mysql/" rel="bookmark">Getting All ZIP Codes In A Given Radius From A Known Point / ZIP Code Via PHP And MySQL</a> (5.9)</li>
			</ol>
	<p class="note">The numbers inside parentheses are relevance scores. Scoring is based, in order of priority, on title, category, content and tags. The higher the score, the more likely that post relates to this post.
	</div>
]]></description>
			<content:encoded><![CDATA[<p>I received an email yesterday from a reader who needed help with implementing my blog post, &#8220;<a href="http://www.dougv.com/2009/07/13/calculating-the-bearing-and-compass-rose-direction-between-two-latitude-longitude-coordinates-in-php/" title="Calculating The Bearing And Compass Rose Direction Between Two Latitude / Longitude Coordinates In PHP">Calculating The Bearing And Compass Rose Direction Between Two Latitude / Longitude Coordinates In PHP</a>.&#8221;</p>
<p>His problem: He has latitude and longitude coordinates in his database tables that are in degrees-minutes-seconds (DMS) format, rather than decimal format. In other words, his coordinates look more like <a href="http://maps.google.com/maps?q=55%C2%B045%E2%80%9906%E2%80%B3N,+37%C2%B037%E2%80%9904%E2%80%B3E&#038;ie=UTF8&#038;hnear=0x46b54a56b84a9be7:0x68ec5d2a865073a,%2B55%C2%B0+45'+6.05%22,+%2B37%C2%B0+37'+1.87%22&#038;gl=us&#038;t=h&#038;z=14" target="_blank">55&deg; 45&#8242; 06&#8243; N, 37&deg; 37&#8242; 04&#8243; E</a> than <a href="http://maps.google.com/maps?q=55.751667,+37.617778&#038;hl=en&#038;sll=37.0625,-95.677068&#038;sspn=59.249168,135.263672&#038;t=h&#038;z=16" title="The Kremlin" target="_blank">55.751667, 37.617778</a>.</p>
<p>So, my reader needed a way to convert back and forth between the two.</p>
<p>Fortunately, that&#8217;s easily done with a couple PHP functions. And since there&#8217;s not a lot that comes up in a Google search about how to make those conversions in PHP (lots of standalone converters, some JavaScript code, little PHP), I&#8217;ll fill the void here.</p>
<p>I should also note this function can be used for my other PHP latitude / longitude post, &#8220;<a href="http://www.dougv.com/2009/03/27/getting-all-zip-codes-in-a-given-radius-from-a-known-point-zip-code-via-php-and-mysql/" title="Getting All ZIP Codes In A Given Radius From A Known Point / ZIP Code Via PHP And MySQL">Getting All ZIP Codes In A Given Radius From A Known Point / ZIP Code Via PHP And MySQL</a>.&#8221;<br />
<span id="more-4315"></span></p>
<h3>Converting From DMS To Decimal</h3>
<p>The easier of the two conversions is going from degrees-minutes-seconds to decimal. That&#8217;s because just as in an hour of time, each degree has 60 minutes, and each minute has 60 seconds. </p>
<p>So, to convert from DMS to decimal, we simply start with the degrees; then add the minutes value divided by 60, and the seconds value divided by 3600 (60 * 60). If the coordinates are South (latitude) or West (longitude), we need to make the decimal coordinate negative; North and East coordinates are positive.</p>
<p>Thus, a North latitude or East longitude is converted as:</p>
<pre class="brush: php; title: ; notranslate">$decimal = $degrees + ($minutes / 60) + ($seconds / 3600);</pre>
<p>A South latitude or West longitude is calculated as:</p>
<pre class="brush: php; title: ; notranslate">$decimal = ($degrees + ($minutes / 60) + ($seconds / 3600)) * -1;</pre>
<p>Here&#8217;s a simple function that makes these conversions. It takes, as arguments, the degrees, minutes and seconds of either a latitude or longitude coordinate, as well as its direction (N, S, E or W), and returns a decimal response.</p>
<pre class="brush: php; title: ; notranslate">
function DMS2Decimal($degrees = 0, $minutes = 0, $seconds = 0, $direction = 'n') {
	//converts DMS coordinates to decimal
	//returns false on bad inputs, decimal on success

	//direction must be n, s, e or w, case-insensitive
	$d = strtolower($direction);
	$ok = array('n', 's', 'e', 'w');

	//degrees must be integer between 0 and 180
	if(!is_numeric($degrees) || $degrees &lt; 0 || $degrees &gt; 180) {
		$decimal = false;
	}
	//minutes must be integer or float between 0 and 59
	elseif(!is_numeric($minutes) || $minutes &lt; 0 || $minutes &gt; 59) {
		$decimal = false;
	}
	//seconds must be integer or float between 0 and 59
	elseif(!is_numeric($seconds) || $seconds &lt; 0 || $seconds &gt; 59) {
		$decimal = false;
	}
	elseif(!in_array($d, $ok)) {
		$decimal = false;
	}
	else {
		//inputs clean, calculate
		$decimal = $degrees + ($minutes / 60) + ($seconds / 3600);

		//reverse for south or west coordinates; north is assumed
		if($d == 's' || $d == 'w') {
			$decimal *= -1;
		}
	}

	return $decimal;
}
</pre>
<p>You can test this function here: <a href="http://www.dougv.com/demo/php_lat_lon_conversion/dms2decimal.php">http://www.dougv.com/demo/php_lat_lon_conversion/dms2decimal.php</a></p>
<p>You call this function as you would any built-in PHP function. For example, assuming you have DMS coordinate values stored in the PHP variables $deg, $min, $sec and $dir, you could assign a value to a decimal equivalent variable, $dec, thus:</p>
<pre class="brush: php; title: ; notranslate">$dec = DMS2Decimal($deg, $min, $sec, $dir);</pre>
<h3>Converting Decimal To DMS</h3>
<p>Going from decimal coordinates to degrees-minutes-seconds is a bit more complicated.</p>
<ul>
<li>Set the DMS direction to South (negative latitude), West (negative longitude), North (positive latitude) or East (positive longitude);</li>
<li>set the decimal to its absolute (i.e., unsigned) value;</li>
<li>set degrees to be the <a href="http://php.net/manual/en/function.floor.php" target="_blank">floor</a> of the decimal;</li>
<li>subtract degrees from decimal, to get its fractional portion;</li>
<li>multiply that by 3600, to get the total number of seconds;</li>
<li>divide the seconds by 60, to get the total number of minutes;</li>
<li>subtract the total number of minutes, times 60, from seconds; then</li>
<li>set seconds value to its floor.</li>
</ul>
<p>To accomplish this task via a PHP function, we will pass to it six arguments: the decimal coordinate; variables for degrees, minutes, seconds and direction; and a Boolean indicating if this is a latitude or longitude coordinate.</p>
<p>Four of our six arguments &#8212;  degrees, minutes, seconds and direction &#8212; will be <a href="http://php.net/manual/en/language.references.pass.php" target="_blank">passed by reference</a>. What that means to a layman is, the values of those arguments will be set by the function, without the need to specifically set them via an evaluation.</p>
<p>In other words, in PHP, you usually set a variable value via a function like this:</p>
<pre class="brush: php; title: ; notranslate">$foo = myfunction($bar);</pre>
<p>But we&#8217;ll do it this way:</p>
<pre class="brush: php; title: ; notranslate">myfunction($bar, $foo);</pre>
<p>An example of that in practice follows the function code.</p>
<pre class="brush: php; title: ; notranslate">
function DecimalToDMS($decimal, &amp;$degrees, &amp;$minutes, &amp;$seconds, &amp;$direction, $type = true) {
	//set default values for variables passed by reference
	$degrees = 0;
	$minutes = 0;
	$seconds = 0;
	$direction = 'X';

	//decimal must be integer or float no larger than 180;
	//type must be Boolean
	if(!is_numeric($decimal) || abs($decimal) &gt; 180 || !is_bool($type)) {
		return false;
	}

	//inputs OK, proceed
	//type is latitude when true, longitude when false

	//set direction; north assumed
	if($type &amp;&amp; $decimal &lt; 0) {
		$direction = 'S';
	}
	elseif(!$type &amp;&amp; $decimal &lt; 0) {
		$direction = 'W';
	}
	elseif(!$type) {
		$direction = 'E';
	}
	else {
		$direction = 'N';
	}

	//get absolute value of decimal
	$d = abs($decimal);

	//get degrees
	$degrees = floor($d);

	//get seconds
	$seconds = ($d - $degrees) * 3600;

	//get minutes
	$minutes = floor($seconds / 60);

	//reset seconds
	$seconds = floor($seconds - ($minutes * 60));
}
</pre>
<p>You can see this in action here: <a href="http://www.dougv.com/demo/php_lat_lon_conversion/index.php">http://www.dougv.com/demo/php_lat_lon_conversion/index.php</a></p>
<p>Let&#8217;s suppose you have the variables $deg, $min, $sec, $dir, for degrees, minutes, seconds and direction, respectively. You also have a decimal coordinate stored in $dec. </p>
<p>This is how you would invoke the function above to assign values to your DMS variables:</p>
<pre class="brush: php; title: ; notranslate">
//if your decimal coordinate is a latitude:
DecimalToDMS($dec, $deg, $min, $sec, $dir, true);

//if your decimal coordinate is a longitude:
DecimalToDMS($dec, $deg, $min, $sec, $dir, false);
</pre>
<p>You can download the demo code here: <a href='http://www.dougv.com/wp-content/uploads/2012/03/php_lat_lon_conversion.zip'>http://www.dougv.com/wp-content/uploads/2012/03/php_lat_lon_conversion.zip</a></p>
<p>All links in this post on delicious: <a href="http://delicious.com/dougvdotcom/converting-latitude-and-longitude-coordinates-between-decimal-and-hours-minutes-seconds" target="_blank">http://delicious.com/dougvdotcom/converting-latitude-and-longitude-coordinates-between-decimal-and-hours-minutes-seconds</a></p>
<div class="yarpp">
	<h5>Related Posts</h5>
		<ol>
				<li><a href="https://www.dougv.com/2009/07/13/calculating-the-bearing-and-compass-rose-direction-between-two-latitude-longitude-coordinates-in-php/" rel="bookmark">Calculating The Bearing And Compass Rose Direction Between Two Latitude / Longitude Coordinates In PHP</a> (43.3)</li>
				<li><a href="https://www.dougv.com/2006/12/20/displaying-a-random-yahoo-search-every-30-seconds-with-javascript-and-php/" rel="bookmark">Displaying A Random Yahoo! Search Every 30 Seconds With JavaScript And PHP</a> (15.4)</li>
				<li><a href="https://www.dougv.com/2009/03/27/getting-all-zip-codes-in-a-given-radius-from-a-known-point-zip-code-via-php-and-mysql/" rel="bookmark">Getting All ZIP Codes In A Given Radius From A Known Point / ZIP Code Via PHP And MySQL</a> (5.9)</li>
			</ol>
	<p class="note">The numbers inside parentheses are relevance scores. Scoring is based, in order of priority, on title, category, content and tags. The higher the score, the more likely that post relates to this post.</p>
	</div>

	Tags: <a href="https://www.dougv.com/tag/converters/" title="converters" rel="tag">converters</a>, <a href="https://www.dougv.com/tag/google/" title="Google" rel="tag">Google</a>, <a href="https://www.dougv.com/tag/latitude-longitude/" title="latitude / longitude" rel="tag">latitude / longitude</a><br />
]]></content:encoded>
			<wfw:commentRss>https://www.dougv.com/2012/03/07/converting-latitude-and-longitude-coordinates-between-decimal-and-degrees-minutes-seconds/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Subscribe To Seth Godin&#8217;s Blog. Do It Now.</title>
		<link>https://www.dougv.com/2012/02/25/subscribe-to-seth-godins-blog-do-it-now/</link>
		<comments>https://www.dougv.com/2012/02/25/subscribe-to-seth-godins-blog-do-it-now/#comments</comments>
		<pubDate>Sat, 25 Feb 2012 15:29:49 +0000</pubDate>
		<dc:creator>Doug Vanderweide</dc:creator>
				<category><![CDATA[Best Practices]]></category>
		<category><![CDATA[Business]]></category>
		<category><![CDATA[Customer Relations]]></category>
		<category><![CDATA[Self Employment]]></category>
		<category><![CDATA[Social Media]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[brands / identity]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[Google Reader]]></category>
		<category><![CDATA[leadership]]></category>
		<category><![CDATA[marketing]]></category>
		<category><![CDATA[productivity]]></category>
		<category><![CDATA[reputation]]></category>
		<category><![CDATA[RSS]]></category>
		<category><![CDATA[Seth Godin]]></category>

		<guid isPermaLink="false">http://www.dougv.com/?p=4130</guid>
		<description><![CDATA[Seth Godin's blog is an invaluable resource for independent contractors and social media marketers. Subscribe to it now.<div class="yarpp">
	<h5>Related Posts</h5>
		<ol>
				<li><a href="https://www.dougv.com/2012/01/01/tumblr-mangles-developer-relations/" rel="bookmark">Tumblr Mangles Developer Relations</a> (7.3)</li>
			</ol>
	<p class="note">The numbers inside parentheses are relevance scores. Scoring is based, in order of priority, on title, category, content and tags. The higher the score, the more likely that post relates to this post.
	</div>
]]></description>
			<content:encoded><![CDATA[<p>If you aren&#8217;t reading <a href="http://www.ted.com/speakers/seth_godin.html" target="_blank">Seth Godin&#8217;s</a> <a href="http://sethgodin.typepad.com/" target="_blank">blog</a>, you ought to be. Especially if you&#8217;re an entrepreneur, independent contractor, in sales or a client-facing role, or are otherwise responsible for leadership or the bottom line.</p>
<p><img class="alignright size-full wp-image-4139" title="Seth Godin" src="http://www.dougv.com/wp-content/uploads/2012/02/Seth_Godin.jpg" alt="Seth Godin" width="150" height="218" />Since that describes pretty much every developer, probably you should be checking out Seth&#8217;s Blog.</p>
<p>He posts once a day, usually in the morning. Which is actually <a href="http://sethgodin.typepad.com/seths_blog/2012/01/the-first-thing-you-do-when-you-sit-down-at-the-computer.html" target="_blank">a trick he recently blogged about</a>: Rather than looking at Twitter or Facebook or whatever first thing, and thus <em>following</em>, make a point of doing something &#8212; anything &#8212; productive, first thing, so that you&#8217;re <em>leading</em>. Then you can check out your social media channels.</p>
<p>I&#8217;m trying to learn that habit; it&#8217;s difficult, but it does make a huge difference in terms of productivity.</p>
<p>Detractors and naysayers consider Godin&#8217;s posts tripe and self-promotion. Sure, some of it can come off as a bit pandering, important or simplistic. Don&#8217;t confuse the words for the message. And it&#8217;s always good to be reminded of the basics.</p>
<p>I subscribe to <a href="http://feeds.feedburner.com/typepad/sethsmainblog" target="_blank">Godin&#8217;s RSS feed</a> via <a href="http://www.google.com/reader/" target="_blank">Google Reader</a>. He notes blog posts on Twitter at <a href="https://twitter.com/#!/thisissethsblog" target="_blank">@ThisIsSethsBlog</a> and on <a href="https://www.facebook.com/sethgodin" target="_blank">his Facebook page</a>.</p>
<p>However you get Godin&#8217;s thoughts, get them. You&#8217;ll be doing yourself a favor.</p>
<p>All links in this post on delicious: <a href="http://delicious.com/dougvdotcom/subscribe-to-seth-godins-blog-do-it-now" target="_blank">http://delicious.com/dougvdotcom/subscribe-to-seth-godins-blog-do-it-now</a></p>
<div class="yarpp">
	<h5>Related Posts</h5>
		<ol>
				<li><a href="https://www.dougv.com/2012/01/01/tumblr-mangles-developer-relations/" rel="bookmark">Tumblr Mangles Developer Relations</a> (7.3)</li>
			</ol>
	<p class="note">The numbers inside parentheses are relevance scores. Scoring is based, in order of priority, on title, category, content and tags. The higher the score, the more likely that post relates to this post.</p>
	</div>

	Tags: <a href="https://www.dougv.com/tag/blogging/" title="blogging" rel="tag">blogging</a>, <a href="https://www.dougv.com/tag/brand-identity/" title="brands / identity" rel="tag">brands / identity</a>, <a href="https://www.dougv.com/tag/google/" title="Google" rel="tag">Google</a>, <a href="https://www.dougv.com/tag/google-reader/" title="Google Reader" rel="tag">Google Reader</a>, <a href="https://www.dougv.com/tag/leadership/" title="leadership" rel="tag">leadership</a>, <a href="https://www.dougv.com/tag/marketing/" title="marketing" rel="tag">marketing</a>, <a href="https://www.dougv.com/tag/productivity/" title="productivity" rel="tag">productivity</a>, <a href="https://www.dougv.com/tag/reputation/" title="reputation" rel="tag">reputation</a>, <a href="https://www.dougv.com/tag/rss/" title="RSS" rel="tag">RSS</a>, <a href="https://www.dougv.com/tag/seth-godin/" title="Seth Godin" rel="tag">Seth Godin</a><br />
]]></content:encoded>
			<wfw:commentRss>https://www.dougv.com/2012/02/25/subscribe-to-seth-godins-blog-do-it-now/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Tumblr Mangles Developer Relations</title>
		<link>https://www.dougv.com/2012/01/01/tumblr-mangles-developer-relations/</link>
		<comments>https://www.dougv.com/2012/01/01/tumblr-mangles-developer-relations/#comments</comments>
		<pubDate>Sun, 01 Jan 2012 19:15:53 +0000</pubDate>
		<dc:creator>Doug Vanderweide</dc:creator>
				<category><![CDATA[API]]></category>
		<category><![CDATA[Business]]></category>
		<category><![CDATA[Customer Relations]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[coding standards]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[hacking]]></category>
		<category><![CDATA[MySpace]]></category>
		<category><![CDATA[privacy]]></category>
		<category><![CDATA[reputation]]></category>
		<category><![CDATA[Tumblr]]></category>
		<category><![CDATA[WebKit]]></category>

		<guid isPermaLink="false">http://www.dougv.com/?p=4060</guid>
		<description><![CDATA[Tumblr warns users against using missing e. That message is chilling to Tumblr API developers.<div class="yarpp">
	<h5>Related Posts</h5>
		<ol>
				<li><a href="https://www.dougv.com/2011/02/17/the-lessons-we-should-all-relearn-from-hbgary/" rel="bookmark">The Lessons We Should All Relearn From HBGary</a> (5)</li>
			</ol>
	<p class="note">The numbers inside parentheses are relevance scores. Scoring is based, in order of priority, on title, category, content and tags. The higher the score, the more likely that post relates to this post.
	</div>
]]></description>
			<content:encoded><![CDATA[<p>Last week I logged on to <a href="http://www.tumblr.com" target="_blank">Tumblr</a> and was confronted with this abomination:</p>
<div id="attachment_4061" class="wp-caption alignnone" style="width: 673px"><img class="size-full wp-image-4061 " title="missing e notice from tumblr" src="http://www.dougv.com/wp-content/uploads/2012/01/Untitled1.jpg" alt="missing e notice from tumblr" width="663" height="672" /><p class="wp-caption-text">Missing e notice from tumblr. Way to encourage API development, guys.</p></div>
<p>Needless to say, this is pretty disturbing, and I wonder what Tumblr is thinking by posting this.</p>
<h3><span id="more-4060"></span>Background</h3>
<p>Some background: <a href="http://www.tumblr.com/about" target="_blank">Tumblr is a blogging site</a>, with <a href="http://www.tumblr.com/why-tumblr" target="_blank">social media overtones</a>. Basically, you can easily follow other bloggers&#8217; posts through a dashboard / search posts via tags, and it&#8217;s quite easy to repost material you find on other blogs.</p>
<p>Like most other major providers, <a href="http://www.tumblr.com/docs/en/api/v2" target="_blank">Tumblr maintains an API</a>. Until last year, it was mostly restricted to retrieving and submitting posts; it was recently expanded to allow some manipulation of blog settings and managing followers.</p>
<p>I like Tumblr a lot. I&#8217;ll go on a couple of times a day, and like most other Tumblr blogs, <a href="http://dougv.info/" target="_blank">my blog</a> is mostly reposts; it&#8217;s where I&#8217;ll dump links / reposts of things I see on the Web that I want to share.</p>
<p><a href="http://missinge.infraware.ca/" target="_blank">Missing e</a> has been around for a while. It&#8217;s a browser add-on for Webkit-enabled browsers; as its name implies, it leverages the API with some neat features that aren&#8217;t directly available through Tumblr itself.</p>
<p>For example, Tumblr has a lot of image posts. Missing e includes a magnifier feature that lets one see a full-sized image right from his dashboard, rather than having to engage in the several clicks it takes to see a full-sized image. Missing e also lets me more easily reblog items (including the automatic addition of tags to reblogged posts), manage my post queue, and otherwise make Tumblr easier to use.</p>
<p>I should note that I don&#8217;t know the developer of this plugin personally, nor have I spoken to him about this notice. (I have <a href="http://blog.missinge.infraware.ca/post/15090130182/tumblrs-support-problem-with-missing-e-repeated" target="_blank">read his response</a> to this outrage, however, and I find it remarkably calm, fair and responsible.) I don&#8217;t know if Tumblr has contacted him about its concerns or tried working with him on those issues (reading the developer&#8217;s responses, it sure sounds like they haven&#8217;t).</p>
<p>I also haven&#8217;t contacted Tumblr about this. I&#8217;m not interested in hearing whatever nonsense they intend to proffer as justification. I know what I read and I know how I feel about it as an API developer.</p>
<p>To Tumblr&#8217;s credit, they haven&#8217;t cut off API access to the plugin, which was certainly <a title="The Danger Of API Development: Making Something Too Good" href="http://www.dougv.com/2011/03/22/the-danger-of-api-development-making-something-too-good/" target="_blank">an option others might have pursued</a>. It wouldn&#8217;t surprise me if a number of Tumblr users can&#8217;t tell where Tumblr ends and missing e begins, and thus they are swamped with support requests they can&#8217;t do much about. And it does make sense to me that missing e uses a lot of resources to accomplish its tasks.</p>
<h3>A Completely Wrong-Headed Approach</h3>
<p>That&#8217;s where my empathy for Tumblr&#8217;s plight ends.</p>
<p>First, <a href="http://royal.pingdom.com/2011/12/15/the-most-reliable-and-unreliable-blogging-services-of-2011/" target="_blank">Tumblr&#8217;s reliability</a>, both in terms of its primary service and its API uptime, <a href="http://stats.pingdom.com/wx4vra365911/23773/history" target="_blank">rivals Twitter</a> for embarrassingly inadequate. (At least Twitter has the common sense to not blame third-party developers for their failure to stay up.)</p>
<p>That&#8217;s on Tumblr alone. It&#8217;s up to them to keep their service running.</p>
<p>I especially find odious the insinuations contained in this notice. While missing e is, in the base definition, a &#8220;hack&#8221; of Tumblr, the tone of this message suggests that the plugin isn&#8217;t well-written and may be up to no good.</p>
<p>Well, <a href="https://github.com/jcutler/Missing-E" target="_blank">you can go to GitHub and look at the code yourself</a>. Yes, it sends data to intermediary servers. Yes, it is technically possible for missing e to steal a user&#8217;s Tumblr credentials, to track Tumblr users&#8217; activities, to obtain personally identifiable information, etc.</p>
<p>Let me be clear: <a href="http://blog.hiramiya.me/post/15081113653" target="_blank">I agree with another user</a> that missing e in no way compromises user information right now. However, it could do so, by virtue of being a browser add-on; to that extent, the notice Tumblr posted is accurate, as they don&#8217;t directly accuse missing e of privacy violations, but do note it is possible for browser plugins to capture information a user never anticipated having captured.</p>
<p>Absent proof that there is an intention behind missing e to do that specifically, and to use such information for nefarious purposes &#8212; evidence Tumblr clearly could provide, if it existed &#8212; I find the tone of this note beyond insulting; it&#8217;s chilling.</p>
<p>My interpretation of this notice is, &#8220;We don&#8217;t like missing e. We&#8217;d just as soon ban it. But that&#8217;s not very Web 2.0 and it&#8217;s likely to generate PR static. So we&#8217;ll scare you, push you toward getting rid of it, then continue to serve those who want to use it.&#8221;</p>
<p>That&#8217;s being a dick. That&#8217;s being lazy. That&#8217;s being stupid.</p>
<h3><img class="alignright size-medium wp-image-4066" title="tumblr_logo" src="http://www.dougv.com/wp-content/uploads/2012/01/tumblr_logo-350x91.png" alt="" width="350" height="91" />A Proper Response</h3>
<p>Were I in charge at Tumblr, we&#8217;d be going about this in an entirely different way.</p>
<ul>
<li>The first thing we would have done is offered the guy who wrote missing e a job.</li>
<li>If not that, we would have offered to buy missing e outright.</li>
<li>And if that didn&#8217;t pan out, we&#8217;d ask missing e users to rate its features, then build those into our platform.</li>
</ul>
<p>Because what does Tumblr&#8217;s approach to this issue say? It says, &#8220;We aren&#8217;t interested in the reasons why missing e is a problem for us. We don&#8217;t care about our end users and why so many of them are using this plugin. It&#8217;s not that our product is inferior, and someone has made it better; it&#8217;s that we have what we have, and even though it can clearly be better, we&#8217;re more interested in the status quo.&#8221;</p>
<p>Or, as I&#8217;ll coin it, <a href="http://www.fool.com/investing/general/2011/07/03/this-is-what-really-killed-myspace.aspx" target="_blank">The MySpace Response</a>: &#8220;Do what you like, so long it fits in our picture of our service.&#8221;</p>
<p>You saw how well that worked out for them.</p>
<p>I don&#8217;t suspect this blog post will cause any change whatsoever in Tumblr&#8217;s approach. I simply want to lament what is an absurd and insulting response to a relatively minor problem by a company that I expected knew better than that.</p>
<p>If Google brought you here because you&#8217;re worried about that notice, suffice it to say that I looked at the missing e code and, as of this writing, I see nothing there to be concerned about.</p>
<p>I do, however, see a lot to be concerned about in Tumblr&#8217;s handling of this matter.</p>
<p>All links in this post on delicious: <a href="http://delicious.com/dougvdotcom/tumblr-mangles-developer-relations" target="_blank">http://delicious.com/dougvdotcom/tumblr-mangles-developer-relations</a></p>
<div class="yarpp">
	<h5>Related Posts</h5>
		<ol>
				<li><a href="https://www.dougv.com/2011/02/17/the-lessons-we-should-all-relearn-from-hbgary/" rel="bookmark">The Lessons We Should All Relearn From HBGary</a> (5)</li>
			</ol>
	<p class="note">The numbers inside parentheses are relevance scores. Scoring is based, in order of priority, on title, category, content and tags. The higher the score, the more likely that post relates to this post.</p>
	</div>

	Tags: <a href="https://www.dougv.com/tag/blogging/" title="blogging" rel="tag">blogging</a>, <a href="https://www.dougv.com/tag/coding-standards/" title="coding standards" rel="tag">coding standards</a>, <a href="https://www.dougv.com/tag/google/" title="Google" rel="tag">Google</a>, <a href="https://www.dougv.com/tag/hacking/" title="hacking" rel="tag">hacking</a>, <a href="https://www.dougv.com/tag/myspace/" title="MySpace" rel="tag">MySpace</a>, <a href="https://www.dougv.com/tag/privacy/" title="privacy" rel="tag">privacy</a>, <a href="https://www.dougv.com/tag/reputation/" title="reputation" rel="tag">reputation</a>, <a href="https://www.dougv.com/tag/tumblr/" title="Tumblr" rel="tag">Tumblr</a>, <a href="https://www.dougv.com/tag/webkit/" title="WebKit" rel="tag">WebKit</a><br />
]]></content:encoded>
			<wfw:commentRss>https://www.dougv.com/2012/01/01/tumblr-mangles-developer-relations/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>New England GiveCamp 2011: What A Weekend!</title>
		<link>https://www.dougv.com/2011/05/03/new-england-givecamp-2011-what-a-weekend/</link>
		<comments>https://www.dougv.com/2011/05/03/new-england-givecamp-2011-what-a-weekend/#comments</comments>
		<pubDate>Tue, 03 May 2011 22:40:04 +0000</pubDate>
		<dc:creator>Doug Vanderweide</dc:creator>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[Databases]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Transact-SQL]]></category>
		<category><![CDATA[crowdsourcing]]></category>
		<category><![CDATA[ethics]]></category>
		<category><![CDATA[GiveCamp]]></category>
		<category><![CDATA[GoDaddy]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[graphic design]]></category>
		<category><![CDATA[Jim O'Neil]]></category>
		<category><![CDATA[marketing]]></category>
		<category><![CDATA[MSDN]]></category>
		<category><![CDATA[Visual Studio]]></category>

		<guid isPermaLink="false">http://www.dougv.com/?p=3849</guid>
		<description><![CDATA[New England GiveCamp 2011 was a great experience! Here's how the weekend went for my team.<div class="yarpp">
	<h5>Related Posts</h5>
		<ol>
				<li><a href="https://www.dougv.com/2010/06/10/at-new-england-givecamp-this-weekend/" rel="bookmark">At New England GiveCamp This Weekend</a> (30.9)</li>
				<li><a href="https://www.dougv.com/2010/06/16/new-england-givecamp-2010-what-a-great-experience/" rel="bookmark">New England GiveCamp 2010: What A Great Experience</a> (21.7)</li>
				<li><a href="https://www.dougv.com/2010/05/13/designers-and-developers-donate-your-time-talent-at-new-england-give-camp-june-11-13-2010/" rel="bookmark">Designers And Developers: Donate Your Time, Talent At New England GiveCamp, June 11-13, 2010</a> (21.1)</li>
			</ol>
	<p class="note">The numbers inside parentheses are relevance scores. Scoring is based, in order of priority, on title, category, content and tags. The higher the score, the more likely that post relates to this post.
	</div>
]]></description>
			<content:encoded><![CDATA[<p>Last weekend was <a href="http://newenglandgivecamp.org/" target="_blank">New England GiveCamp</a> <a href="http://newenglandgivecamp.org/category/give-camp-2011/" target="_blank">2011</a>, in which 100+ developers, designers and other volunteers gathered to donate time and skills to some 30 charities who needed IT help.</p>
<p>This year, I was project lead for <a href="http://www.alexsteamfoundation.com/" target="_blank"><strong>Alex&#8217;s Team Foundation</strong></a>, based in Andover, Mass. Our team was <a href="https://twitter.com/#!/saush11" target="_blank"><strong>Saurabh Moondhra</strong></a> and <a href="http://dirigosoftwaresolutions.com" target="_blank"><strong>William Wade</strong></a>, both experienced ASP.NET developers.</p>
<p>Alex&#8217;s Team Foundation, named after 16-year-old Alex Miliotis, who passed away from leukemia in 2002, raises money to support nurses and other oncology professionals, and supports youth sports. The foundation is largely the labors of <strong>Patti Rae Miliotis</strong>, Alex&#8217;s mother, and a handful of reliable volunteers. Like every small nonprofit, Alex&#8217;s Team doesn&#8217;t have a lot of money.</p>
<div id="attachment_3853" class="wp-caption alignnone" style="width: 710px"><img class="size-full wp-image-3853" title="Alex's Team Foundation at New England GiveCamp 2011" src="http://www.dougv.com/wp-content/uploads/2011/05/negc2011_08.png" alt="Alex's Team Foundation at New England GiveCamp 2011" width="700" height="245" /><p class="wp-caption-text">From the right to left: William Wade, Doug Vanderweide, Saurabh Moondhra and Patti Rae Miliotis of Alex&#39;s Team Foundation. The lady with her feet up is Deanna Lohnes, who worked on another project; the woman in green, whose name I do not know, was her charity&#39;s contact person.</p></div>
<p>Like every other leader of a small nonprofit, Patti is pulled in a lot of different directions and has all she can do to keep track of the people with whom she comes in contact, nonetheless all the donations she gets. Patti also hosts a few events every year. She basically needs a way to keep track of who attends those events or otherwise supports her organization, and to mail merge thank-you notes.</p>
<p>So that was the project I led this weekend: Converting a bunch of data stored in (of course!) Excel spreadsheets into a more relational database, with the ability to export that data in order to mail merge thank-you and fundraising letters.</p>
<p><span id="more-3849"></span></p>
<h3>CiviCRM: Too Much, Too Late</h3>
<p>At first, I thought we could use <a href="http://civicrm.org/" target="_blank">CiviCRM</a>, which is a Joomla / Drupal extension to manage membership, events, capital campaigns, email lists and the like.</p>
<p>The primary benefit in it, to my thinking, was that it does most of what Patti needed done; so, if she could use it, most of the work would already be done. That would allow us not only to import her spreadsheets, but also pay some attention to re-skinning the foundation&#8217;s current Web site (which is sorely needed).</p>
<p>CiviCRM&#8217;s biggest drawback is a huge learning curve. It is not easy to understand out of the box, especially if someone doesn&#8217;t have prior experience with customer relation management software. That was Patti&#8217;s problem: It was too daunting, much like the system she had tried previously.</p>
<p>So our task played directly to our strengths: Make an easy-to-use contact database on the Web, which could also track donations and event registrations.</p>
<h3>Friday: Three Ways To Skin A Cat</h3>
<p>Patti asked that we use Access to make her database, since she has some experience with it. Unfortunately, nobody on our team knew much about Access, and at least two of us have had negative experiences with it.</p>
<p>At last year&#8217;s GiveCamp, William wound up having to become an on-the-spot Access developer, and it left something of a sour taste. I&#8217;ve never had much success building standalone Access databases, largely because I find its visual query and form designers extremely limiting. Saurabh said he had little to no experience with Access.</p>
<p>The one thing we all knew was <a href="http://www.asp.net/web-forms" target="_blank">ASP.NET Web Forms</a>. Well, to be specific, we knew three different versions of ASP.NET Web Forms. Saurabh programs in C#. William and I prefer VB.NET. William likes using <a href="http://msdn.microsoft.com/en-us/netframework/aa904594" target="_blank">Linq</a>; I prefer old-school stored procedures; Saurabh likes inline SQL statements.</p>
<p>The good thing in that is, an ASP.NET Web application doesn&#8217;t need to run under just one language, and so long as the connections and queries are good, you can mix and match database layers to your hearts&#8217; content.</p>
<p>So we settled in to design a database schema that could record contacts, donations and events. That consumed most of Friday night&#8217;s efforts. Here&#8217;s how it looked on the whiteboard. <em>(Saurabh took this with his cell phone camera. Click it for full-size, which is easier to read.)</em></p>
<p><a href="http://www.dougv.com/wp-content/uploads/2011/05/Photo_CB6DA206-0E0E-C34E-DADC-2CD1B7D8C086.jpg"><img class="alignnone size-large wp-image-3858" title="Our database schema" src="http://www.dougv.com/wp-content/uploads/2011/05/Photo_CB6DA206-0E0E-C34E-DADC-2CD1B7D8C086-728x545.jpg" alt="Our database schema" width="728" height="545" /></a></p>
<h3>Saturday: Tackling The Obstacles</h3>
<p>Saturday started with a lot of little hiccups.</p>
<p><a href="http://discountasp.net/" target="_blank">DiscountASP.NET</a> very kindly donated the Web hosting for our application. However, they require a domain name in order to host a site, and wouldn&#8217;t donate that. Because we weren&#8217;t redesigning the Alex&#8217;s Team site, which needed to remain on its current server, we couldn&#8217;t use the one domain name Patti had. So I popped on over to GoDaddy and bought a special domain for the occasion.</p>
<p>With a domain in hand, we could go ahead and create our application. Except that DiscountASP.NET, which would also donate a SQL Server database, has a separate ordering process for add-on services, which meant we would be delayed for several hours while we waited for the database to be set up.</p>
<p>Since our entire application was basically just a GUI to a relational database, we really couldn&#8217;t get much done without a database. So I tried to use one of my free GoDaddy Web hosting credits to set up service sufficient to get us started.</p>
<p>Unfortunately, I couldn&#8217;t use the newly registered domain to access any free service, because I guess the registration was too new; also, all the other domains I have registered through GoDaddy point to something, so I couldn&#8217;t use one of those.</p>
<p>Thankfully, GoDaddy will sell Web hosting for $8 for one month without need for a contract, so I just went ahead with that; and within 30 minutes, we were able to build our database and start making forms.</p>
<div class="aside">For the record, DiscountASP.NET came through for us completely by early Saturday afternoon. It was only my panic, lack of timely preparation / organization and impatience that made me use GoDaddy. This application will soon be moved to DiscountASP.NET, where it will permanently reside. I&#8217;ve used DiscountASP.NET for Web hosting in the past and in terms of features and reliability, they simply cannot be outdone.</div>
<ul>
<li>I didn&#8217;t pre-install Visual Studio on my crappy laptop; that took two hours out of Saturday morning. A new laptop will definitely be in the offing for next year.</li>
<li>While waiting for that install to finish, I went on a wild goose chase for a convenience store in Cambridge. Suffice it to say, the one nearest to NERD is a 7-Eleven about a half-mile away; but I wound up hanging around for about 45 minutes outside <a href="http://store.thecoop.com/" target="_blank">the Coop</a>, waiting for it to open, because I was mistakenly led to believe it sold tobacco. (I fell off the no-smoking wagon for the weekend; I knew there would be some stress, and nicotine definitely keeps me calm.) So what should have been a 20-minute break turned in to an hour and a half.</li>
</ul>
<div id="attachment_3862" class="wp-caption alignnone" style="width: 717px"><img class="size-full wp-image-3862" title="Saurabh accuses me of being high. It's not an unreasonable assumption." src="http://www.dougv.com/wp-content/uploads/2011/05/tweet01.jpg" alt="Saurabh accuses me of being high. It's not an unreasonable assumption." width="707" height="378" /><p class="wp-caption-text">I was gone so long in search of smokes, Saurabh accused me of going to get stoned. It was a reasonably accurate portrayal.</p></div>
<ul>
<li>Somewhere around 1 pm, I mistakenly thought we would have to work in ASP.NET 2.0 in order to resolve some namespace and other issues, and I communicated as much to my team. This led them to delete what they had made to that point. Within 5 minutes, William had corrected my error. Saurabh was nonplussed.</li>
<li>After dinner, we were ready to start integrating our files. Non unexpectedly, we ran into a number of small issues. It took a little while to get William&#8217;s files working on my laptop, but try as we might, we just couldn&#8217;t get Saurabh&#8217;s C#-based solution to work. So he wound up converting them to VB.NET. I went back to the hotel to finish my part of the solution.</li>
</ul>
<p>On Saturday night, I gave a quick overview on WordPress for the non-profits that I fear was a real snoozer. (Memo to self: Never host a seminar after dinner.) During that, I won my choice of a number of software prizes; but what was left available in the first-come, first-served prize pool didn&#8217;t really thrill me.</p>
<p>William had his eye on a copy of <a href="http://www.devexpress.com/Subscriptions/DXperience/editionEnt.xml" target="_blank">DevExpress DXPerience Enterprise</a>, a bunch of .NET controls, worth about $1,300 on the retail market. I seldom program in Windows and he does all the time, so I transferred my winnings to him.</p>
<h3>A Word About The Royal Sonesta</h3>
<p>While many GiveCampers &#8212; William and Saurabh among them &#8212; spent the night at NERD, I stayed at a hotel, because I&#8217;m too old and fussy to be sleeping on hard floors. Also, I&#8217;m in Cambridge, which is quite a treat for me, and I wanted a chance to spend at least a little time in the city each night, even if it was just walking a half-mile to the hotel and having a drink or two in the lounge. So I opted to stay at the <a href="http://www.sonesta.com/boston/" target="_blank">Royal Sonesta</a>, which had the best rates.</p>
<p><iframe src="http://maps.google.com/maps/ms?hl=&amp;ie=UTF8&amp;msa=0&amp;msid=208401827101352337890.0004a2659aa672a475969&amp;ll=42.364727,-71.085362&amp;spn=0.011098,0.031242&amp;z=15&amp;output=embed" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" width="728" height="350"></iframe><br />
<small>View <a style="color: #0000ff; text-align: left;" href="http://maps.google.com/maps/ms?hl=&amp;ie=UTF8&amp;msa=0&amp;msid=208401827101352337890.0004a2659aa672a475969&amp;ll=42.364727,-71.085362&amp;spn=0.011098,0.031242&amp;z=15&amp;source=embed">New England GiveCamp 2011</a> in a larger map</small></p>
<p>This simple plot was foiled, however, by the Royal Sonesta&#8217;s unfathomable renting out of the first-floor lounge on both Friday and Saturday night. On Friday, I was able to get a single beer, but it felt very strange crashing a private event and I wound up calling it a night early. On Saturday, they just turned me away, period, because a wedding reception was being held there.</p>
<p>Also, while the Sonesta loudly advertised its local shuttle service, they require you to register for it; and nobody saw fit to remind me of it at the concierge desk, when I asked for a shuttle schedule. As a result, I stood outside for a half-hour on Friday night, waiting for a shuttle that never showed up.</p>
<p>Overall, the Sonesta&#8217;s staff was courteous and friendly; especially the front desk, porters and valets. But the concierges were inexplicably surly, brusque, unapologetic and unhelpful.</p>
<p>So while the price was great ($144 per night, amazingly good for Cambridge), the hotel was clean and the room / amenities nice, I cannot recommend the place. I mean, what kind of hotel closes its lounge to guests? And has concierge service that basically looks to do as little as possible, and that with a frown on its face?</p>
<h3>Sunday: It All Comes Together, Like Magic</h3>
<p>The great benefit of having competent, skilled and motivated team members is that their sense of urgency and their ability to adapt and overcome adversity are both fine-tuned. That was certainly the case with William and Saurabh; they were pretty much done by Sunday morning, and all we had to do was take one more crack at getting all three parts to play nicely with one another.</p>
<p>I let that task fall to William, and he did an extraordinary job getting everything to work; certainly, a much better job than I would have done. Meanwhile, Saurabh settled in to help Patti make some upgrades to her Web site, notably in getting her Paypal donation button working again, adding in some alternative donation options and adding Google Analytics to her site.</p>
<p>Saurabh also won an instant drawing on Sunday and scored a copy of Age of Empires III for PC. I had my heart set on winning a WiFi-enabled Kindle, which was being given away at the end of GiveCamp; but that didn&#8217;t happen.</p>
<p>Not to say we didn&#8217;t get some great swag:</p>
<ul>
<li>A full-boat copy of Microsoft Office 2010 Professional. Awesome!</li>
<li>A thumb drive. You can never have too many of those.</li>
<li>A set of jewelers&#8217; screwdrivers. Again, you can never have too many of them.</li>
<li>A screw-top sippy cup with durable straw. Which is exactly what my spill-prone self needs to have on his work desk.</li>
<li>A miniature Nerf football. Awesome stress reliever.</li>
<li>A pen and pad of paper. Again, never can have enough of either.</li>
<li>A puzzle book. Not that I had time during the weekend to mess with puzzles.</li>
<li>A GiveCamp T-shirt. I like T-shirts as mementos.</li>
</ul>
<p>During the weekend, Jim O&#8217;Neil dropped scores of additional treats on a giveaway table, many of which were O&#8217;Reilly books. I managed to get a spare puzzle book, several notepads, a <a href="http://www.amazon.com/Oracle-Language-Pocket-Reference-OReilly/dp/0596514042/" target="_blank">PL/SQL quick pocket reference</a>, <a href="http://www.amazon.com/Access-2010-Missing-Matthew-MacDonald/dp/1449382371/" target="_blank">Access 2010: the missing manual</a> and <a href="http://www.amazon.com/Microsoft®-Visual-Basic®-2010-Microsoft/dp/0735626693/" target="_blank">Visual Basic 2010 Step By Step</a>. There were a number of other books available but I was too slow to score them. However, I understand I am allowed a free ebook from O&#8217;Reilly, as well.</p>
<p>The bottom line is that we got our project done, it works, Patti seems fairly pleased with it and we had a great time this weekend. It&#8217;s definitely renewed my commitment; I&#8217;ll be a GiveCamper until I can&#8217;t do it any more.</p>
<p>All links in this post on delicious: <a href="http://www.delicious.com/dougvdotcom/new-england-givecamp-2011-what-a-weekend" target="_blank">http://www.delicious.com/dougvdotcom/new-england-givecamp-2011-what-a-weekend</a></p>
<div class="yarpp">
	<h5>Related Posts</h5>
		<ol>
				<li><a href="https://www.dougv.com/2010/06/10/at-new-england-givecamp-this-weekend/" rel="bookmark">At New England GiveCamp This Weekend</a> (30.9)</li>
				<li><a href="https://www.dougv.com/2010/06/16/new-england-givecamp-2010-what-a-great-experience/" rel="bookmark">New England GiveCamp 2010: What A Great Experience</a> (21.7)</li>
				<li><a href="https://www.dougv.com/2010/05/13/designers-and-developers-donate-your-time-talent-at-new-england-give-camp-june-11-13-2010/" rel="bookmark">Designers And Developers: Donate Your Time, Talent At New England GiveCamp, June 11-13, 2010</a> (21.1)</li>
			</ol>
	<p class="note">The numbers inside parentheses are relevance scores. Scoring is based, in order of priority, on title, category, content and tags. The higher the score, the more likely that post relates to this post.</p>
	</div>

	Tags: <a href="https://www.dougv.com/tag/crowdsourcing/" title="crowdsourcing" rel="tag">crowdsourcing</a>, <a href="https://www.dougv.com/tag/ethics/" title="ethics" rel="tag">ethics</a>, <a href="https://www.dougv.com/tag/givecamp/" title="GiveCamp" rel="tag">GiveCamp</a>, <a href="https://www.dougv.com/tag/godaddy/" title="GoDaddy" rel="tag">GoDaddy</a>, <a href="https://www.dougv.com/tag/google/" title="Google" rel="tag">Google</a>, <a href="https://www.dougv.com/tag/graphic-design/" title="graphic design" rel="tag">graphic design</a>, <a href="https://www.dougv.com/tag/jim-oneil/" title="Jim O&#039;Neil" rel="tag">Jim O&#039;Neil</a>, <a href="https://www.dougv.com/tag/marketing/" title="marketing" rel="tag">marketing</a>, <a href="https://www.dougv.com/tag/msdn/" title="MSDN" rel="tag">MSDN</a>, <a href="https://www.dougv.com/tag/visual-studio/" title="Visual Studio" rel="tag">Visual Studio</a><br />
]]></content:encoded>
			<wfw:commentRss>https://www.dougv.com/2011/05/03/new-england-givecamp-2011-what-a-weekend/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Automatically Hash Tagging Text With PHP And MySQL</title>
		<link>https://www.dougv.com/2011/04/11/automatically-hash-tagging-text-with-php-and-mysql/</link>
		<comments>https://www.dougv.com/2011/04/11/automatically-hash-tagging-text-with-php-and-mysql/#comments</comments>
		<pubDate>Tue, 12 Apr 2011 02:12:18 +0000</pubDate>
		<dc:creator>Doug Vanderweide</dc:creator>
				<category><![CDATA[Databases]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Social Media]]></category>
		<category><![CDATA[Twitter]]></category>
		<category><![CDATA[arrays]]></category>
		<category><![CDATA[elegance]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[Google Reader]]></category>
		<category><![CDATA[hacking]]></category>
		<category><![CDATA[hashtags]]></category>
		<category><![CDATA[regular expression]]></category>

		<guid isPermaLink="false">http://www.dougv.com/?p=3794</guid>
		<description><![CDATA[Use PHP to extract terms from a MySQL database table and automatically tag an input string with those terms.<div class="yarpp">
	<h5>Related Posts</h5>
		<ol>
				<li><a href="https://www.dougv.com/2009/06/13/sorting-your-mysql-results-set-in-php-using-jquery-and-a-more-traditional-approach/" rel="bookmark">Sorting Your MySQL Results Set In PHP Using jQuery (And A More Traditional Approach)</a> (17.4)</li>
				<li><a href="https://www.dougv.com/2008/12/09/a-simple-page-click-count-system-using-php-and-mysql/" rel="bookmark">A Simple Page Click Count System Using PHP And MySQL</a> (17.2)</li>
				<li><a href="https://www.dougv.com/2007/11/15/multilingual-web-pages-via-php-arrays-and-mysql/" rel="bookmark">Multilingual Web Pages Via PHP, Arrays And MySQL</a> (16.4)</li>
			</ol>
	<p class="note">The numbers inside parentheses are relevance scores. Scoring is based, in order of priority, on title, category, content and tags. The higher the score, the more likely that post relates to this post.
	</div>
]]></description>
			<content:encoded><![CDATA[<p>My <a href="http://www.dougv.com/2011/01/03/update-to-the-yourls-twitter-google-reader-script/">recent work on the Google Reader to Twitter interface</a> led me to recognize a serious shortcoming of such a basic system: A lack of support for hash tags.</p>
<p>For those unfamiliar with Twitter, <a href="http://en.wikipedia.org/wiki/Tag_%28metadata%29">hashtags</a> are basically words proceeded by a hash mark (#). When a word is &#8220;tagged&#8221;, it becomes a hyperlink to content also containing that term.  </p>
<p>Tagging isn&#8217;t unique to Twitter. It&#8217;s integral to WordPress, Tumblr and many other blogging platforms; Google uses tags (which they call &#8220;labels&#8221;) in most of their major applications, including GMail and Google Documents.</p>
<p>The reason is simple: People tend to organize information in terms of categories, so interrelating content by linking items that belong to the same categories to one another makes it easier on us to find and process that information.</p>
<p>So here&#8217;s a quick and easy script that lets you take keywords / tags / labels / categories / what have you from a MySQL table, run those terms over a string / subject text, and automatically tag that string  with those terms.</p>
<p>(In a later tutorial, I will describe how to add new terms to the database.)</p>
<p><span id="more-3794"></span>
<div class="aside"><strong>An aside on what constitutes a &#8220;term&#8221;:</strong> The one thing that became readily apparent during this project was that there are a lot of different trade-offs required in determining what constitutes a &#8220;term,&#8221; and in how easy it is to select simple derivatives of a given term in a subject string.</p>
<p>For example, <em>hack</em>. We probably want to be able to tag the similar terms <em>hacks, hacker, hackers, hacking and hacked, </em>as well as more complex derivatives, such as <em>h4x0r. </em>Needless to say, it&#8217;s difficult to convert <em>hack </em>into <em>h4x0r</em>, but it&#8217;s also difficult to simply append common endings to the root word. (More on this when we cover, in an upcoming post, adding terms from a subject string to the database.)</p>
<p>It&#8217;s also hard to know when <em>hack </em>is actually in a context we want to hash tag. For example, <em>hacker </em>is probably always going to be a term we want to tag. But words such as <em>hackle, hackberrry </em>and <em>hacksaw </em>are not ones we&#8217;re likely to want to tag, if the context in which we&#8217;re using <em>hack </em>is that of &#8220;altering a system to perform differently than intended.&#8221;</p>
<p>The compromise I am using is not the most elegant, but it is simple and direct: A <em>term </em>is as an exact match of a word contained in the database. Therefore, if I want to tag <em>hack, hacker, hacking </em>and <em>hacked</em>, all four of those words must appear in the database.</p>
<p>Terms are case-insensitive. In other words, if I have <em>hack </em>in the database, it matches <em>hack, Hack, hAck </em>and <em>HACK </em>in the subject string.</div>
<h4>An HTML Form To Input A Subject String</h4>
<p>We need a simple way to get our subject string (that is, the text we want to have tagged). Here&#8217;s a form to do that; you could, of course, alter this script to open up a file, or retrieve data from some other store, as your subject text.</p>
<p>I am also including an echo statement, just before the form, that will show the autotagged text once the form has been submitted.</p>
<pre class="brush: xml; title: ; notranslate">&lt;p class=&quot;notice&quot;&gt;&lt;?php echo $content; ?&gt;&lt;/p&gt;
&lt;form id=&quot;tform&quot; name=&quot;tform&quot; action=&quot;&lt;?php echo $_SERVER['PHP_SELF']; ?&gt;&quot; method=&quot;post&quot;&gt;
	&lt;textarea id=&quot;ttext&quot; name=&quot;ttext&quot; cols=&quot;50&quot; rows=&quot;3&quot;&gt;&lt;?php echo $_POST['ttext']; ?&gt;&lt;/textarea&gt;
	&lt;br /&gt;
	&lt;input type=&quot;submit&quot; name=&quot;submit&quot; id=&quot;submit&quot; value=&quot;Submit&quot; /&gt;
&lt;/form&gt;</pre>
<h4>A MySQL Table To Contain Terms</h4>
<p>We need to have some sort of data store to hold the terms. Eventually, we&#8217;re going to put these terms into an array, so you could simply hard-code your terms as a PHP array. Also, you could use an XML file, JSON, a CSV or other text file, etc. to hold your terms.</p>
<p>In my case, I am storing terms in a MySQL table. Here&#8217;s the code for my table:</p>
<pre class="brush: sql; title: ; notranslate">CREATE TABLE IF NOT EXISTS `php_auto_hashtag` (
  `term_text` varchar(255) NOT NULL,
  UNIQUE KEY `term_text` (`term_text`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

INSERT INTO `php_auto_hashtag` (`term_text`) VALUES
('adsense'),('amazon'),('android'),('aol'),('api'),('apple'),
('bing'),('canvas'),('cbs'),('chrome'),('cloud'),('comcast'),
('darpa'),('eff'),('facebook'),('firefox'),('google'),
('hacker'),('hackers'),('hacking'),('html'),('html5'),
('http'),('https'),('ie9'),('ietf'),('intel'),('internet'),
('ios'),('ipad'),('ipv6'),('javascript'),('kinect'),('malware'),
('microsoft'),('mozilla'),('mvc'),('nokia'),('pentagon'),('php'),
('ps3'),('rackspace'),('safari'),('silverlight'),('sony'),
('stuxnet'),('symbian'),('tablets'),('twitter'),('vb'),
('verizon'),('virus'),('windows'),('xml'),('youtube');</pre>
<p>Note that we don&#8217;t have a primary key. That&#8217;s because we have a unique key. We don&#8217;t want the same term in the database twice, and that&#8217;s what a unique key does: prevent duplicate entries. As a result, a primary key isn&#8217;t necessary for tuning / optimization if we have a unique key, since their purposes in indexing are similar.</p>
<h4>A PHP Function To Retrieve Terms From The Database</h4>
<p>To get the terms out of the database and into a PHP array, I&#8217;ll make a function. The reason why I am doing it this way will be noted shortly. The function returns false on an error, an array on success.</p>
<p>The function assumes the database table contains at least one term, but if it doesn&#8217;t, it&#8217;s not a fatal error (but will show a warning to the end user).</p>
<p>Finally, you&#8217;ll note I am using <a href="http://php.net/manual/en/function.define.php">globally defined constants</a> for taking in database credentials. This isn&#8217;t really elegant, but I want I want this script to work out-of-the-box for those who have limited programming skills; by defining DB variables globally, an end user can simply plug in the right values and use this script out of the box.</p>
<pre class="brush: php; title: ; notranslate">//your database server variables
define('MYSQL_HOST', 'localhost');
define('MYSQL_USER', 'db_user');
define('MYSQL_PASS', 'db_password');
define('MYSQL_DB', 'db_name');
define('MYSQL_QUERY', 'SELECT term_text FROM php_auto_hashtag');

function at_get_terms() {
	//retrieve terms from database
	//returns Boolean false on failure, array of terms on success

	if(!$link = mysql_connect(MYSQL_HOST, MYSQL_USER, MYSQL_PASS)) {
		trigger_error('function at_get_terms: Cannot connect to database server. Please check your host name and credentials', E_USER_WARNING);
		return false;
	}

	if(!mysql_select_db(MYSQL_DB)) {
		trigger_error('function at_get_terms: Cannot select the database. Please check your database name', E_USER_WARNING);
		return false;
	}

	if(!$rs = mysql_query(MYSQL_QUERY)) {
		trigger_error('function at_get_terms: Error parsing query. MySQL error: ' . mysql_error(), E_USER_WARNING);
		return false;
	}

	if(mysql_num_rows($rs) == 0) {
		trigger_error('function at_get_terms: No terms found in database', E_USER_NOTICE);
		return false;
	}

	$out = array();
	while($row = mysql_fetch_array($rs)) {
		$out[] = $row[0];
	}
	return $out;
}</pre>
<h4>A PHP Function To Autotag The Subject</h4>
<p>We can now create a function that does the autotagging. It takes as arguments the subject text and the array of terms we want tagged; it returns false on an error and the tagged subject string on success.</p>
<p>In this case, we&#8217;re using <a href="http://us2.php.net/manual/en/function.preg-replace.php">preg_replace</a> to do the tagging. There&#8217;s a lot of argument as to whether <a href="http://us2.php.net/manual/en/function.str-replace.php">str_replace</a> or <a href="http://us2.php.net/manual/en/function.ereg-replace.php">ereg_replace</a> is faster / better than preg_replace, but I find such arguments to be counting angels dancing on the head of a pin. I use preg_replace because it works quickly enough, regular expressions are an elegant way to find text, and <a href="http://us2.php.net/manual/en/intro.pcre.php">PCRE is PHP&#8217;s preferred regular expression processing extension</a>.</p>
<pre class="brush: php; title: ; notranslate">function autotag($input, $terms) {
	//tags $input with $terms
	//returns false on error, tagged string on success

	if(strlen(trim($input)) &lt; 1) {
		trigger_error('function autotag: string to be tagged is empty', E_USER_WARNING);
		return false;
	}
	if(!is_array($terms)) {
		trigger_error('function autotag: terms is not an array', E_USER_WARNING);
		return false;
	}

	$tmp = array();
	foreach($terms as $term){
		//matches will be terms exactly as in database,
		//followed by space or newline
		$tmp[] = &quot;/($term)(\s|$)/i&quot;;
	}
	$out = preg_replace($tmp, '#$0', $input);
	return $out;
}</pre>
<p>Note the second argument in the preg_replace call, above. # is just the hash mark, which in the case of Twitter will be turned automatically into an tag link. $0 means, in regular expressions, the entire part of the subject text (the third argument) that matched the pattern (the first part of the argument). </p>
<p>So, if you wanted to use hyperlinks instead of hashtags, and use the found terms as querystring variables to a page named term.php, your preg_replace statement would be something like this:</p>
<pre class="brush: php; title: ; notranslate">
	$out = preg_replace($tmp, '&lt;a href=&quot;term.php?term=$0&quot;&gt;$0&lt;/a&gt;', $input);
</pre>
<p>(<strong>Always sanitize your querystring variables before using them in your PHP code.</strong> You have been warned. Don&#8217;t come crying to me or pointing fingers in my direction if you fall victim to an <a href="http://en.wikipedia.org/wiki/Cross-site_scripting">XSS</a> or <a href="http://en.wikipedia.org/wiki/Code_injection">injection</a> attack. <strong>Sanitize your variables.</strong>)</p>
<h4>Get The Terms And Tag The Target String</h4>
<p>We now have everything we need to autotag the target string. It&#8217;s as simple as a single-command if statement:</p>
<pre class="brush: php; title: ; notranslate">
$content = &quot;Enter text in the textarea below, then click Submit. The text will be automatically tagged with terms contained in the database. &quot;;

if(isset($_POST['submit'])) {
	$content = &quot;&lt;strong&gt;Hashtagged string:&lt;/strong&gt; &quot; . autotag(htmlspecialchars($_POST['ttext']), at_get_terms());
}
</pre>
<p>And that&#8217;s all there is to it. You can see a working demo here: <a href="http://www.dougv.com/demo/php_auto_hashtag/">http://www.dougv.com/demo/php_auto_hashtag/</a></p>
<p><a href="http://www.dougv.com/wp-content/uploads/2011/04/php_auto_hashtag.zip">You can also download the source code</a>. I distribute this code under the GNU GPL version 3.</p>
<p>All links in this post on delicious: <a href="http://www.delicious.com/dougvdotcom/automatically-hash-tagging-text-with-php-and-mysql">http://www.delicious.com/dougvdotcom/automatically-hash-tagging-text-with-php-and-mysql</a></p>
<div class="yarpp">
	<h5>Related Posts</h5>
		<ol>
				<li><a href="https://www.dougv.com/2009/06/13/sorting-your-mysql-results-set-in-php-using-jquery-and-a-more-traditional-approach/" rel="bookmark">Sorting Your MySQL Results Set In PHP Using jQuery (And A More Traditional Approach)</a> (17.4)</li>
				<li><a href="https://www.dougv.com/2008/12/09/a-simple-page-click-count-system-using-php-and-mysql/" rel="bookmark">A Simple Page Click Count System Using PHP And MySQL</a> (17.2)</li>
				<li><a href="https://www.dougv.com/2007/11/15/multilingual-web-pages-via-php-arrays-and-mysql/" rel="bookmark">Multilingual Web Pages Via PHP, Arrays And MySQL</a> (16.4)</li>
			</ol>
	<p class="note">The numbers inside parentheses are relevance scores. Scoring is based, in order of priority, on title, category, content and tags. The higher the score, the more likely that post relates to this post.</p>
	</div>

	Tags: <a href="https://www.dougv.com/tag/arrays/" title="arrays" rel="tag">arrays</a>, <a href="https://www.dougv.com/tag/elegance/" title="elegance" rel="tag">elegance</a>, <a href="https://www.dougv.com/tag/google/" title="Google" rel="tag">Google</a>, <a href="https://www.dougv.com/tag/google-reader/" title="Google Reader" rel="tag">Google Reader</a>, <a href="https://www.dougv.com/tag/hacking/" title="hacking" rel="tag">hacking</a>, <a href="https://www.dougv.com/tag/hashtags/" title="hashtags" rel="tag">hashtags</a>, <a href="https://www.dougv.com/tag/regular-expression/" title="regular expression" rel="tag">regular expression</a><br />
]]></content:encoded>
			<wfw:commentRss>https://www.dougv.com/2011/04/11/automatically-hash-tagging-text-with-php-and-mysql/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Hello, I&#8217;ve a question about your post &#8220;Using AJAX To Update A Non-Map DIV Via Google Maps API&#8217;s GDownload() And GMarker OnClick Event&#8221;. Your example is for the V2 version of google maps, so, is it possible to have exactly the same things for the v3 api?</title>
		<link>https://www.dougv.com/2011/03/18/hello-ive-a-question-about-your-post-using-ajax-to-update-a-non-map-div-via-google-maps-apis-gdownload-and-gmarker-onclick-event-your-example-is-for-the-v2-version-of-google-maps/</link>
		<comments>https://www.dougv.com/2011/03/18/hello-ive-a-question-about-your-post-using-ajax-to-update-a-non-map-div-via-google-maps-apis-gdownload-and-gmarker-onclick-event-your-example-is-for-the-v2-version-of-google-maps/#comments</comments>
		<pubDate>Fri, 18 Mar 2011 15:24:24 +0000</pubDate>
		<dc:creator>formspring.me</dc:creator>
				<category><![CDATA[API]]></category>
		<category><![CDATA[FormSpring Questions]]></category>
		<category><![CDATA[Google Maps API]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Google]]></category>

		<guid isPermaLink="false">http://www.dougv.com/2011/03/18/hello-ive-a-question-about-your-post-using-ajax-to-update-a-non-map-div-via-google-maps-apis-gdownload-and-gmarker-onclick-event-your-example-is-for-the-v2-version-of-google-maps/</guid>
		<description><![CDATA[I believe the version 2 methods and objects I used in the referenced Google Maps API blog post will work in version 3. But I'm not certain.<div class="yarpp">
	<h5>Related Posts</h5>
		<ol>
				<li><a href="https://www.dougv.com/2007/07/12/using-ajax-to-update-a-non-map-div-via-google-maps-apis-gdownload-and-gmarker-onclick-event/" rel="bookmark">Using AJAX To Update A Non-Map DIV Via Google Maps API&#8217;s GDownload() And GMarker OnClick Event</a> (42.6)</li>
				<li><a href="https://www.dougv.com/2010/12/30/using-yourls-and-the-twitter-api-with-google-readers-custom-sendto-link/" rel="bookmark">Using YOURLS And The Twitter API With Google Reader&#8217;s Custom SendTo Link</a> (18.7)</li>
				<li><a href="https://www.dougv.com/2010/02/03/hacking-wp-plugins-used-to-remove-plugin-version-numbers/" rel="bookmark">Hacking WP-PluginsUsed To Remove Plugin Version Numbers</a> (15.5)</li>
			</ol>
	<p class="note">The numbers inside parentheses are relevance scores. Scoring is based, in order of priority, on title, category, content and tags. The higher the score, the more likely that post relates to this post.
	</div>
]]></description>
			<content:encoded><![CDATA[<p class="formspringmeAnswer">I believe the objects and methods are the same between the two versions of the API, so the answer is probably. Unless, of course, they objects and / or methods have changed. In which case, no.</p>
<p class="formspringmeFooter">
    <a href="http://www.formspring.me/dougvdotcom?utm_medium=social&amp;utm_source=wordpress&amp;utm_campaign=shareanswer">Ask me anything</a></p>
<div class="yarpp">
	<h5>Related Posts</h5>
		<ol>
				<li><a href="https://www.dougv.com/2007/07/12/using-ajax-to-update-a-non-map-div-via-google-maps-apis-gdownload-and-gmarker-onclick-event/" rel="bookmark">Using AJAX To Update A Non-Map DIV Via Google Maps API&#8217;s GDownload() And GMarker OnClick Event</a> (42.6)</li>
				<li><a href="https://www.dougv.com/2010/12/30/using-yourls-and-the-twitter-api-with-google-readers-custom-sendto-link/" rel="bookmark">Using YOURLS And The Twitter API With Google Reader&#8217;s Custom SendTo Link</a> (18.7)</li>
				<li><a href="https://www.dougv.com/2010/02/03/hacking-wp-plugins-used-to-remove-plugin-version-numbers/" rel="bookmark">Hacking WP-PluginsUsed To Remove Plugin Version Numbers</a> (15.5)</li>
			</ol>
	<p class="note">The numbers inside parentheses are relevance scores. Scoring is based, in order of priority, on title, category, content and tags. The higher the score, the more likely that post relates to this post.</p>
	</div>

	Tags: <a href="https://www.dougv.com/tag/google/" title="Google" rel="tag">Google</a><br />
]]></content:encoded>
			<wfw:commentRss>https://www.dougv.com/2011/03/18/hello-ive-a-question-about-your-post-using-ajax-to-update-a-non-map-div-via-google-maps-apis-gdownload-and-gmarker-onclick-event-your-example-is-for-the-v2-version-of-google-maps/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

