Widget Lessons
So yesterday my staff were working on building Javascript widgets that allow piece of our website content to be inserted into other systems easily. Primarily we’ve been working on this for our staff listing which appears in both the public website and our intranet. The solution has been to build a web service to the data which is part of our public website and then build a javascript which can easily be inserted into a page, block, whatever in Drupal.
My developer hit one small snag working on this and that is how to properly insert the HTML where we wanted it. This is because we have to append the HTML we are creating to a tag that already exists. At first we thought “no problem” we’d just append it to div.content. The problem with this is that when you are looking at a single node with the javscript this works great. But if you flip to a view which shows multiple nodes with their body well then the script runs multiple times. This is because every Drupal node has a div.content.
So how do we deal with this? Well we create our own unique div that we have the Javascript insert the content into. It looks something like this
<div id=”staffTableGoesHere”> </div>
<script src=”http://devinfo.lib.uh.edu/api/api.js”></script> <script>$(document).ready(function() {callAPI(‘displayAll’,'c.staff’,'dc.identifier=1′);});</script> <noscript><a href=”http://devinfo.lib.uh.edu/api/?format=HTML&component=c.staff&method=displayAll&query=dc.identifier=1″>View Basic Version</a></noscript>
I’ve always wondered why some widgets include a blank div and now I know why. For example, several of the WorldCat widgets do this. Without the div you can guarantee that the content you want to insert will get put in the right place.
Another solution is to put an id into the script tag and use that id to add the HTML after the script tag. That would look like this
<script src=”http://devinfo.lib.uh.edu/api/api.js”></script> <script>$(document).ready(function() {callAPI(‘displayAll’,'c.staff’,'dc.identifier=1′);} id=”staffTableGoesHere”);</script> <noscript><a href=”http://devinfo.lib.uh.edu/api/?format=HTML&component=c.staff&method=displayAll&query=dc.identifier=1″>View Basic Version</a></noscript>
Two simple solution but the logic takes a bit to parse out.
Another option might be to have the JS add the content with document.write. I think that’s actually what I’ve done with Xerxes. Is there a downside to this I’m not thinking of?
The problem wasn’t “how” to add the content. But where to put it. You want someone to be able to cut and paste a javascript widget into a page and the content to appear EXACTLY where the javascript is placed without you knowing anything about the page they are placing stuff into.
document.write will work but it doesn’t make for lean JS. Particularly in this case where a table of staff info is being created on the fly. Also, we intend that the JS which is cut and pasted be as simplistic as possible and that all formatting take place in an external script.
Right, I meant that (let’s see if the blog comments allow tags, hmm, I’ll try escaing them:
<div id=”foo”> </div>
<script>
stuff();
<script>
Should be equivalent to:
<script>
document.write(“<div id=’foo’> </div>”);
stuff()
</script>
Is there a reason to prefer one to the other? The latter seems to keep the script tag simpler for pasting in, to me. Becuase really, everything that’s in between those script files can normally be in an external js file href referenced (including the document.write itself). So that’s why I had the inclination to use the document.write technique, precisely to keep the content which is cut-and-pasted as simple and short as possible, and NOT require the content that’s cut-and-pasted to contain a raw div, so it can contain ONLY script tag(s).
On the other hand, there’s this voice in the back of my head that says “NEVER use document.write!” I’m not sure if never really means never though.
You could also, of course, write JS to only insert the content in the _first_ or _last_ div.content seen, for pages with more than one. If that’s the right thing to do. (This kind of thing is a lot easier if you can use prototype or jquery — but the ‘right’ way to use prototype or jquery in a ‘widget’ like this, if there is a right way, is something I’ve thought about and not been sure of the answer. Any opinion?)
OR, you could _maybe_ find the div.content that’s the container of the script tag in which callAPI() is called. Not actually sure if it’s possible.
But sometimes putting the content right where the user says (by including the ‘widget’ that that point) is the right way to do it.
I’ve been doing a bunch of js hacking like this lately too. It’s fun. There’s always a bunch of ways to solve the problem, but the some of the ways end up being a lot more elegant (less code, less fragile when page structure changes) than others, and I don’t always think of the elegant way until the second or third or fourth iteration. And one of the things that makes this kind of js hacking both fun and confusing are indeed the multiple levels of abstraction you need to think through.
Thought about _first or _last but the order of the nodes changes over time and as content is added. So this wouldn’t work. I completely agree there are multiple levels of abstraction that need to be thought through to get this right. The thing that helps me the most is to do use cases that way I can get myself and the developers beyond the specific instance so that the code is reusable. Really helps to think “how would this work in…” and fill in all the systems I can think of.
Well in order to stay “Drupal-ish,” the content will have to be in a block or some other id’ed container anyway. Working from the assumption that you have jquery available to you, you could then target the block’s content div via
$("div#block-block-N div.content")or something like that, where N is the block number (which never changes). This is also a way to assign CSS to a single block.
Huh, Karen, there might be Drupal-isms that are escaping me, but I can’t figure out how including a div in HTML immediately before a script tag — is any different than doing a a document.write to write that same div within the script tag. Seems to me that should produce the exact same output every time, shouldn’t it? I don’t see how drupal matters?
It seems to me to be exactly equivalent — just a question of which seems most elegant/maintainable. For me, it seemed to be reducing the amount of stuff that needed to be copy/pasted, by taking the ‘div’ out of the copy/paste block, and having the script write it instead.
I can’t figure out how those two cases would be any different. But of course, if you put the div in manually in html, you could put it in a _different_ location than the script tag, like put the script tag in the head, but the div where it belongs. That’s certainly one advantage to that technique.