No Mod Required

Sharing is Caring- Using JavaScript to Enable Image Sharing

People hijack my images all over the web. Yes. It's true. While I'm not 100% happy about it, I'm also not 100% upset about it since I'm glad that people like my images enough to swipe them. So I tolerate it.

One thing that would make it all a lot more palatable is if people who were taking my images would link back to my site. The link would serve as credit, would drive traffic, and would increase my page rank. What's not to love?

To that end, I wrote up a little script last week that I attached to all my gallery images to maybe, just maybe, help people to do just that.

Want to try it out? Click through to view the glory of my "trouble" graffiti canvas. When you're there, right click on the image- as if you were going to "Copy Image Location."

Wasn't that cool?

For the lazybones in the audience who haven't actually clicked through, right clicking on the image triggers a little JavaScript function that throws this at the user

sharing-is-caring.jpg

As you can see it's a fairly friendly option to grab some easy-to- paste code in order to share the image. How nice am I?

If anyone actually uses it instead of just stealing the image then the whole process would be a resounding success. Traffic + page rank = good times.

Want to know how it's done? The code follows. For what it's worth, I've translated this into plain old JavaScript. There are some shortcuts that my actual code uses that I've turned back into native objects and methods so that it should be more readily grasped by folks no matter what style or library they might use in their day-to-day coding:

Anyway, on page load (either window.onload or whatever enhanced version you use- I use this ) I run this block:

if (document.body.id=="grafpg" || document.body.id=="comicspg" || document.body.id=="artpg" ) {
document.body.innerHTML+="<div id='msg'><div id='msg-body'><h3>Want to share this image?</h3><label for='grablink'>Here's the code to paste into your blog, forum post, myspace, etc :</label> <textarea name='textarea' id='grablink'></textarea><p id='download'></p><p>All images &copy; Rob Larsen</p></div><div id='clear-msg'></div></div><div id='cloak'></div>";
createShareLinks();
}

In my specific case I know that pages with ids of grafpg, comicspg and artpg are the page types that will have gallery images I want to manipulate. If any of those three page IDs show up, I use innerHTML to append the basic structure of the overlay to the page. As a note, because I have this structure already in place, this script is completely unobtrusive. I didn't need to change the HTML at all to implement this functionality. Such is the benefit of thinking a little bit about the semantic meaning of ID and CLASS attributes. Since went so far as to separate the three "types" of art page, even though they display exactly the same, I could conceivably present three separate versions of the interface depending on the site section, still without touching the underlying HTML. Structure is cool.

I should mention I love this sledgehammer approach to appending content to a page. I admit it wholeheartedly. It's just so damned direct I can't help but smile. Don't get me wrong, I love createElement, appendChild, etc. and use them all the time. It's just, being an HTML guy at heart I love to be able to mock up a display like this and then just drop it into a page with very little fuss. HTML source to JavaScript flashiness in no time flat :)

Anyway, after appending the overlay divs, I initiate the guts of the behavior with the createShareLinks() function. The source of which follows:

function createShareLinks(){
// there can be more than one img on a gallery page. Get the full nodeList of images
   var theImg = document.getElementById("artimg").getElementsByTagName("img");
// and loop through
   for ( var i=0;i<theImg.length;i++ ){
// attach an event to the contextmenu event
// did you know there was such a thing?
      theImg[i].oncontextmenu = function() {
// do come calculations to create the proper
// height and width for the cloak and msg divs;
         document.getElementById("cloak").style.width = document.getElementById("msg").style.width = this.offsetWidth  + 2 + "px";
         document.getElementById("cloak").style.height = document.getElementById("msg").style.height = this.offsetHeight + 2 + "px";
// there's some padding we need to take into account
         document.getElementById("msg-body").style.height = this.offsetHeight - 32  + "px";
// get the x and y of the image and set the
// top and left of the divs accordingly
         document.getElementById("cloak").style.left = document.getElementById("msg").style.left = (findX(this) -1) +"px";
         document.getElementById("cloak").style.top =  document.getElementById("msg").style.top =  (findY(this) - 1) +"px";
// build the link text.
         document.getElementById("grablink").value="<a href='http://www.drunkenfist.com/'><img src='"+this.src+"'  alt='image copyright rob larsen' /></a>";
// I just like this
         document.getElementById("grablink").onfocus = function() {
            this.select();
         };
         document.getElementById("download").innerHTML="<a href='"+this.src+"'>Download this file</a>";
         document.getElementById("clear-msg").onclick= clearShareLinks;
// show the divs
         document.getElementById("cloak").style.display = document.getElementById("msg").style.display = "block";
         return false;
      }
   }
};
//a simple function to hide the overlay;
function clearShareLinks() {
   document.getElementById("cloak").style.display = document.getElementById("msg").style.display = "none";
}

First I create a nodeList of all images contained in div id="artimg." Once I have that nodeList, which will contain either one or two nodes, I loop through and attach an oncontextmenu event handler to the image. Did you know there was an oncontextmenu event handler before reading this article? If not, now you do :) In the function that fires when a user right clicks (or control clicks) on an image I do some basic dHTML goodness. First I set the height and width of the two divs to equal the height and width of the target image and then I use the target image to calculate the left and top of the divs so that they sit right on top of the image. Once that's all set I do some string manipulations to build out the link, some random other housekeeping and then I flip the switch and show the divs to the world in all of their glory.

By the way- are you thinking… "why two divs?"

Well, the one div, the one you'd expect to be the parent, I want to be semi-opaque in order to cloak the image. Thing is, one of the properties of CSS opacity is that all child elements inherit the parent's opacity. Meaning I couldn't have 100% opaque text inside a DIV with 80% opacity. Which would suck since it would be tougher to read. Instead, I fake it with two DIVs styled to act as one. Fun times.

Want to see the CSS?

Here it is:

#msg, #cloak {
  position:absolute;
  width:0;
  height:0;
  left:0;
  top:0;
  display:none;
}
#cloak {
  opacity: .50;
/* IE, if you need this to validate, move into a separate IE sheet */
/* and hide from other browsers/ the validator with conditional comments */
  filter:Alpha(opacity=50);
  z-index:9998;
  background-color: #0099FF;
}
#msg {
  z-index:9999;
}
#msg-body {
  width:75%;
  margin:auto;
  padding-top:35px;
}
#msg-body h3 {
  background: #ddd;
  padding:5px;
  width:100%;
  color:#000;
  border:1px solid #000;
  border-bottom:3px solid #000;
  border-right:3px solid #000;
}
#msg-body p {
  width:100%;
  background: #ddd;
  padding:5px;
  border:1px solid #000;
  border-bottom:3px solid #000;
  border-right:3px solid #000;
}
#msg-body label {
  font-size:.82em;
  font-weight:bold
  display:block;
  padding:5px;
  background: #ddd;
  width:100%;
  margin-bottom:10px;
  border:1px solid #000;
  border-bottom:3px solid #000;
  border-right:3px solid #000;
}
#grablink {
  width:100%;
  height:100px;
  background:#efefef;
  border:1px solid #000;
  border-bottom:3px solid #000;
  border-right:3px solid #000;
  overflow:hidden;
  font-size:10pt;
  padding:4px;
}
#clear-msg {
  position:absolute;
  right:5px;
  top:5px;
  width:30px;
  height:30px;
/* sprites are my friend */
  background: url(http://media.drunkenfist.com/img/sprite.1.png) -597px -185px no-repeat;
/* make it look clickable */
  cursor:pointer;
  cursor:hand
}

And that's that. Will it get used? I hope so. If not, it's the thought that counts, right?

JSON Feeds For Fun and Profit Part 3- wherein Eval() kind of bums me out

(and several months later I finish my little JSON series…)

So far my exploration of JSON has been a fun-filled walk in the park. Moonbeams and rainbows. All that.

This last post on the subject is slightly less cool as I get into one of the least attractive components of the whole JSON thing- the use of eval() to transform a text response into a proper JavaScript object. The use of eval() is one of the reasons I originally was a little shy about using JSON. Why? eval() is slow and I try to stay away from slow if at all possible. That and the idea of eval()-ing code from some third party makes me wary.

Obviously, if you're using a callback, or are just using a built in object reference which inserts itself into memory as soon as the script is attached a script to a page, eval() won't come up, but sometimes there's no other option but to ingest the JSON feed as plain text and eval() it into Object-hood. The feed I was helping out a co-worker with last week (AKA back in August when I originally wrote this) which spurred on this very series of posts was one such feed. It looked something like this example cribbed from JSON.org:

{
    "glossary": {
        "title": "example glossary",
		"GlossDiv": {
            "title": "S",
			"GlossList": {
                "GlossEntry": {
                    "ID": "SGML",
					"SortAs": "SGML",
					"GlossTerm": "Standard Generalized Markup Language",
					"Acronym": "SGML",
					"Abbrev": "ISO 8879:1986",
					"GlossDef": {
                        "para": "A meta-markup language, used to create markup languages such as DocBook.",
						"GlossSeeAlso": ["GML", "XML"]
                    },
					"GlossSee": "markup"
                }
            }
        }
    }
}

As you can see, if you attach that script to the document using a script tag, there's absolutely no way to get anything out of it. No amount of poking at it will communicate the contents of that file to the rest of the scripts on the page. There's no reference in memory and there's no way to create one. It has to be ingested using an XMLHttpRequest and eval() has to bring it into life. Since it uses XHR and not a dynamic script tag, that means a local pass-though script on the server side is needed to get around cross site scripting constraints. In other words, we're looking at a big pile of "why bother?" For my money, if you have to go through these steps, then plain old XML makes more sense.

Anyway, if you have to use JSON and the only option is a response structured like the above, then the following is one way to handle it.

First, an example "tag cloud" hacked together with a raw JSON tag feed from del.icio.us:

Now some code:

<script type="text/javascript">
function ajaj(json_doc,callback) {
  //ajaJ? get it? so clever.
  //create a cross browser XMLHttpRequest object
  //let's do this
  	http= new function() {
  	var xmlhttp;
  	try {
  	//start with the standard
 	 xmlhttp = new XMLHttpRequest();
 	}
 	catch(e) 
  	{
 	 //do the IE thing if the above fails
	  //IE deserves some props since they actually created 
 	 //the whole XMLHttp thing
  	xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
  } 
  return xmlhttp;
  };
  //is it okay to try this?
  if((http.readyState==0)||(http.readyState==4))
  {
  //I do this in a try/catch since  there's 
  //always a chance of some 
  //sort of data error and I want the 
  //possibility of using a fallback function
  //if there is some sort of internet hiccup. 
  //I also hate showing errors to the user
  //In this case I could just drop it since
  //I'm not doing anything special here, but 
  //I'm not about to reinvent the wheel
  //since this is more about JSON 
  //than the way I use XMLHttpRequest 
  try 
  {
  //open the connection
  http.open("GET", json_doc, true);
  //set up a function to handle the response
  http.onreadystatechange = function handle_http_response() 
  {
  //I take no chances.
  //you can have have readyState of 4, a status 
  //of 200 and still run into trouble
  //I want to know all my ducks are in a row :) 
//I'm all belt and suspenders 
  if ((http.readyState == 4)&&(http.status==200)&&(http.responseText!=null)) 
  { 
//First I create an empty object, the same structure as the regular Delicious JSON feed
//I like the structure of their feeds so I'm replicating them here.
  Delicious = {}
  //Use eval to bring the response into the Object
//Again, I'm using Delicious' own Delicious.tags structure
  Delicious.tags=eval('('+http.responseText+')');
  //we've got this handy object now that we've done our eval
  //we pass it as an argument to the callback function
  callback(Delicious);
  }
  }
  }
  catch(e) 
  {
  //we could do some stuff here to handle errors
  }
  //let's put an end to this transaction
  http.send(null);
  }
  }
  //this is the callback function
  function delicious_me(Delicious) {
  //this is an object functioning as 
  //an associative array
  //not a proper indexed array typical of most JavaScript
  //so we use a for... in loop
  //which loops through all the members of the array by named reference
  for (var i in Delicious.tags) {
  //inside the loop i will refer to the property name
  //tags[i] will return the value
  //BTW, the tag cloud is a total hack. 
  //I should do some better math here, 
  //but like the XHR stuff above 
   //this isn't really about a tag cloud...
  document.getElementById("delicious").innerHTML+="<a href='http://del.icio.us/rob_react/"+i+"' style='font-size:"+Delicious.tags[i]/2+"px;line-height:"+Delicious.tags[i]/2+"px;'>"+i+"</a>\n";
  } 
  }

The interesting bit, at least in the scope of this article, is this where we use the eval method to pull the responseText into the Delicious and pass it to the callback function.

Delicious.tags=eval('('+http.responseText+')');
callback(Delicious);

Nothing groundbreaking there and, honestly, not much for me to get excited about. I just don't like the use of eval and would go to great lengths to avoid using it. That said, if you're in a situation where a raw JSON feed is all you've got to work with, this is a way out of the jam.

For my next, and final entry in this I'll use this handy JSON parser for the safe alternative to eval(). That should make me a little happier than this example.

The difference between javascript's getElementById() and getElementsByName()

The above question is one I've gotten in my logs a couple of times. I don't actually answer it anywhere on the site, so I figured I might as well answer it here. For some strange reason, in my mellowed old age, I'm all about helping and I haven't written about web technologies in about a month, so it can't hurt.

The basic answer is this-

  • getElementById() is incredibly useful. If you write any JavaScript at all, you'll likely use it all the time
  • getElementsByName() isn't really useful at all. I've never used it ever and can't really think of a place where I would use it. I rarely use the name attribute and can't really see where I would want to use it in any way that would make sense in the context of this method.

To define them, the specs say

getElementsByName
With HTML 4.01 documents, this method returns the (possibly empty) collection of elements whose name value is given by elementName (The name attribute value for an element.) In [XHTML 1.0] documents, this methods only return the (possibly empty) collection of form controls with matching name. This method is case sensitive.

Which is to say you pass the method a string (the name) and you get back a nodeList , a collection of elements with matching that name.

Which is, as far as my experience goes, a big pile of useless.*

getElementById
Returns the Element whose ID is given by elementId. If no such element exists, returns null. Behavior is not defined if more than one element has this ID.

Which means- pass this method a string (the id) and this method will return either an element matching that id or null. This is probably one of the most used DOM methods in the Javascript universe. Accessing and manipulating elements is what JavaScript is all about and getElementById is the handiest method for accessing an individual element on a page.

*which isn't to say I don't want to hear about a really cool use of it if there's one out there. If you've got one, please share! I love seeing interesting code samples :)

A static variation on JavaScript's getElementsByTagName

As I mentioned yesterday I wanted to offer up an alternative to the browser crushing example I made to illustrate the point about Javascript Objects. So here it is…

Instead of using getElementsByTagName("a") and watching the count grow towards infinity, I simply added a new function which does the same thing as getElementsByTagName, except that it takes a "snapshot" of the nodeList and turns it into a plain old Array. So, unlike a variable created with getElementsByTagName, this one will not track changes to the underlying DOM collection.* I've illustrated this with a count of the members of the two collections before and after the manipulations are run.

Here's a sample:

And here's the function

function getStaticCollectionByTagName(tag, node) {
  //just like getElementsByTagName, we want to open up and optional context node 
  if (node==undefined) {
  //if it's not passed in as an argument, we set it to be document 
  node=document
  }
  //we build a variable with the original collection 
  var temp_array = node.getElementsByTagName(tag);
  //and a new array to hold them 
  var static_results = new Array;
  //then we just loop through and copy each into the new array 
  for (i=0; i<temp_array.length;i++) {
  static_results.push(temp_array[i]);
  }
  //and return it 
  return static_results;
  }

As a note, feel free to comment on any/all of the code articles I write here. I'm mostly writing this stuff to push my own knowledge and understanding so if you see something I can do better, something that I've missed or something that's just plain silly, feel free to let me know :)

*it WILL track the individual objects that make up the collection as those are passed to the new array as Object references.

Things to know: JavaScript Objects are copied by reference not value

I was reading PPK's excellent JavaScript book during lunch and I came across a point in his chapter on Data Types (yes, I'm reading it straight through even if I already know all the basic bits) that I think a lot of people might not understand fully. As he says:

The object data type encompasses everything that's not a number, string, or boolean. It differs from the other three types because objects are copied, passed and compared by reference, not value.

Why is that important? What does that mean? Well, consider the following HTML fragment

<div id="fruit-loops-are-fun">
<p>Fruit Loops!</p>
</div>

and a function that evaluates the following statements

var fruit_loops_div = document.getElementById("fruit-loops-are-fun");
alert(fruit_loops_div.className);

the above returns an empty alert. Empty, of course, because there's no class name associated with that named div.

nothing.png

In the same function, consider now the the next two statements

document.getElementById("fruit-loops-are-fun").className="coco-pops";
alert(fruit_loops_div.className);

that pair returns this

class.png

So, the class is set with the className attribute and, note, when it's set the original reference is used and and not the assigned variable. Even still once the class is set, the variable immediately returns the correct value.

I thought about this for a minute and came up with an interesting illustration of how a lack of understanding of this concept could have catastrophic results. WARNING, this example snippet will lock your browser up, so don't run it :)

This is based on actual work I did for Compete, by the way. I built a Wordpress plugin for them that manipulated all the links on a page in several different ways based on user preferences. In its flashiest form it added some icons that fired a Snap-like* Ajax tooltip showing Compete Snapshot data for every URL on a blog page.

In this example, instead of manipulating the links in that way I've simplified it, deciding to simply add a link after each link on the page pointing to the Compete data for the referenced domain. This is easier to follow code-wise and this it allows me to show a potential pitfall of misunderstanding the topic at hand.

Here's the death code.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  <html xmlns="http://www.w3.org/1999/xhtml">
  <head>
  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
  <title>Endless Loop</title>
  <script type="text/javascript">
  window.onload= function(){
//get all the links
  var all_links = document.getElementsByTagName("a")
 //loop through them and append a compete link
     for (i=0; i<all_links.length;i++) {
          var new_a= document.createElement("a");
          new_a.setAttribute("href","http://siteanalytics.compete.com/"+all_links[i].href);
          var new_text=document.createTextNode("snap shot data for "+all_links[i].href);
          new_a.appendChild(new_text)
          insert_after(all_links[i].parentNode,new_a,all_links[i]);
      }
  }
  function insert_after(parent, node, referenceNode) 
  {
      parent.insertBefore(node, referenceNode.nextSibling);
  }
  </script>
  </head>
  <body>
  <p><a href="http://www.drunkenfist.com/">my one link</a></p>
  </body>
  </html>

Run the above code and your browser will die after being thrown into an infinite loop. That's because the all_links nodeList isn't some sort of static snapshot of document.getElementsByTagName("a") when it's first run (which a person unfamiliar with the concepts involved might assume.) It is document.getElementsByTagName("a") and that collection grows by one every time a new link is added. Therefore, i is never less than all_links.length and your browser instance will give up the ghost. You can see a variation here (I've set a static test, so it will end after < ∞ iterations) that shows the script running out of control.

So there's what not to do :) As a foil for this browser killing code (even if it is educational) I'll write up an example of how to safely do the above operation tomorrow.

[updated]as promised, a follow-up…[/updated]
*I was actually done, or close to done with this project when the Snap plugin was released, so we weren't copying their steez…

Javascript: getElementById() for XML fragments and arbitrary XML documents + getElementsByAttribute()

Why I never wrote this function before is a mystery.

In my experience, people* designing XML response docs for Ajax-y type applications often re-use xHMTL attributes. href and src are common examples. One other one that I've run into pretty often is id. Since Javascript's getElementById() relies on the document's DTD defining the id attribute doing something like

xml_document.getElementById('myid')

returns null when pointed at the sort of informal XML documents people often spit out for these kind of things.

To remedy that, I wrote this:

function getElementByIdMXL(the_node,the_id) {
	//get all the tags in the doc
	node_tags = the_node.getElementsByTagName('*');
	for (i=0;i<node_tags.length;i++) {
	//is there an id attribute?
		if (node_tags[i].hasAttribute('id')) {
			//if there is, test its value
			if (node_tags[i].getAttribute('id') == the_id) {
				//and return it if it matches
				return node_tags[i];
			}
		}
	}
}

where the_node is the DTD-less XML document and the_id is the id to search for. It's a pretty simple script really, but it would have been useful a couple of times in recent memory.

Looking at that bit of code made me want to create a version that searched for arbitrary attribute/value pairs. Since I've looked at getElementsByClass a thousand times it practically wrote itself:

function getElementsByAttribute(the_attribute, the_value, the_node) {
        if ( the_node == null )
             the_node = document;
        var node_tags = the_node.getElementsByTagName('*');
	var results = new Array();
	for (i=0, j=0; i<node_tags.length;i++) {
		if (node_tags[i].hasAttribute(the_attribute)) {
			if (node_tags[i].getAttribute(the_attribute) == the_value) {
			  	results[j] = node_tags[i];
            	                j++;
			}
		}
	}
	return results;
}

The only real difference between the two is the second returns an array and the first just returns the one object.

These aren't really groundbreaking, of course. They're just a couple of bits of code I thought I'd share…

*that definitely includes me