Implementing stateful links Jamis 30 Nov 2005

36 comments Latest by Inge J�rgensen

With the rapid escalation of DHTML and Ajax usage, there are now many instances of a different breed of hyperlink—the “do something” link, rather than the “go somewhere” link. Consider the following bit of HTML that demonstrates a link that toggles the visibility of a companion div. (All examples in this article use the superb prototype.js library, without which I would never, ever use Javascript.)

  <script src="/javascripts/prototype.js"></script>
  <a href="#" onclick="Element.toggle('editBox')">Edit</a>
  <div id="editBox" style="display: none">
    ...
    <a href="#" onclick="Element.toggle('editBox')">Close
  </div>

Initially, only the “Edit” link is visible. Clicking on it causes the “editBox” div to be displayed. Clicking it again will hide the div. Simple. However, the problem is that after clicking the “Edit” link, it still says “Edit”, instead of something more appropriate, like “I’m done editing”.

One solution to this would be to alter the innerHTML of the “Edit” link based on the visibility of the “editBox” div, but this gets ugly as you have more and more stateful links. It also makes it less convenient to change the “active” and “inactive” versions of the text, since at least one of them will be hard-coded somewhere in Javascript.

Here’s a trick we’ve started using in our applications. Put the “active” text (and a CSS class to represent the active state) in custom attributes of the link itself:

  <a href="#" id="editLink" onclick="toggleEdit()"
     activetext="I'm done editing" activeclass="activelink">Edit</a>

This way, both the text and style to use when the link is “active” are right there in the HTML, readily located and modified as needed.

To ensure the custom attributes are used, we wrote the following bit of Javascript:

  Element.activate = function(element, activate) {
    element = $(element)

    // get the inactive text, which defaults to the element's innerHTML
    var inactive_text = element['inactivetext']
    if(!inactive_text) {
      element['inactivetext'] = element.innerHTML
      inactive_text = element.innerHTML
    }

    // get the active text and active class, which default to the current
    // innerHTML and className, respectively
    var active_text = element.getAttribute('activetext') ||
      element.innerHTML
    var active_class = element.getAttribute('activeclass') ||
      element.className

    if(!activate) {
      element.innerHTML = inactive_text
      if(active_class) Element.removeClassName(element, active_class)
      element['active'] = false
    } else {
      element.innerHTML = active_text
      if(active_class) Element.addClassName(element, active_class)
      element['active'] = true
    }
  }

And, finally, the toggleEdit function becomes:

  function toggleEdit() {
    Element.toggle('editBox')
    Element.activate('editLink', !$('editLink')['active'])
  }

On the downside, this technique does mean that your HTML will no longer be valid (due to the use of custom attributes), but you can create and reference a custom DTD if you need that level of standards-compliance.

36 comments so far (Jump to latest)

Dan Boland 30 Nov 05

Thanks for the link.

One thing I do in this instance is replace href=”#” in the link with href=”void(0);”, just to avoid any potential pound sign weirdness.

Darren James Harkness 30 Nov 05

Above should have been:

<a href=”#” onclick=”replaceLinkText(‘I'm done editing’); return false;” id=”editlink”>Edit</a>

Matt 30 Nov 05

This is awesome. Thanks as always Jamis.

Jay Reding 30 Nov 05

It’s a nice trick, but I’d be real reticent to either break the validity of your XHTML or go to the trouble of crafting a custom DTD. One of the things that web design is all about is not going about and breaking the established standards and crafting semantic, standards-based XHTML and CSS.

Would it be possible to use the DOM to attach those attributes to a given element without placing them in the code? Or ideally, creating a custom class that is tied to a DOM script that adds those attributes in to all members of that class? That way you’re not creating non-standard code, but you still have the same level of flexibility as before.

I’m also a big fan of attaching onclick and other handlers via the DOM as well via classes - that way you’re producing the lightest possible HTML and separating behavior from content in the same way one should separate presentation from content.

Mike Rumble 30 Nov 05

Seems like a pretty neat solution.

I’d be interested to see what people think about the fact that this breaks the validation of a page.

For me validation isn’t the be-all and end-all of web development, but it seems like a bit of a backwards step to purposely use code that won’t validate.

How practical is it to write a custom DTD for each page which uses this technique and how much work would that involve?

Jared White 30 Nov 05

Pretty good idea there. I too am not crazy about adding new attributes directly to the HTML — I think I’d opt for passing those two parameters to the Javascript function directly as arguments (and you could save the original version of the link as a JS variable when you need to toggle back).

Eoghan O'Brien 30 Nov 05

That’s a very good idea, I’m also interested to see what people think about the breaks in validation. Cheers

Jamis 30 Nov 05

One of the nice things about putting the information in custom attributes is that I can activate the link from anywhere—not just from the link itself. If I have to pass the data explicitly to a JS function, it becomes trickier. I have to either store the state info in JS variables somewhere (which then distances that information from the location where it has relevance) or I have to duplicate the information in multiple function calls (which is always a bad bad bad thing).

I agree that in general, one should avoid breaking HTML validation. However, it is not a difficult thing to take an existing DTD and add your custom attributes to it, if that’s important to you. Also, I don’t believe it is as critical to avoid adding custom attributes. New tags (and unclosed tags) are evil, IMO, but attributes are just more information about the tags themselves, above and beyond what the DTD creators envisioned, but not dangerously so.

We’re all about embracing constraints here, but in this instance I think relaxing this constraint a little bit gives a greater capacity for maintainability.

At any rate, I’m curious to hear opposing viewpoints on this. Why do you feel that custom attributes ought to be avoided?

Jay Reding 30 Nov 05

My take on custom attributes (and even event handlers for that matter) is that you end up mixing content and behavior. We’ve already established the idea that mixing content and presentation isn’t a good way of going about things. I’m a believer in also separating content and behavior along the same lines. XHTML should be a content layer that’s as free from presentational or behavioral markup as possible.

Granted, if writing a customized DTD is up your alley, there’s no reason why you can’t do that. However, you’re still mixing content and behavior even with a custom DTD.

In general, I’d argue that simply adding the custom attributes without a custom DTD or doing it in the DOM is not a good choice - especially since standards compliance is already an accepted best coding practice.

Kim Siever 30 Nov 05

Wouldn’t it make more sense it replace the # in the href attribute with the location of an actual edit page? This way, non-JavaScript users could still edit.

Jesper 30 Nov 05

First of all: the trick. Links reflecting their state when they act like this is great, clearly. When I create any such link I make sure to implement functionality like this. Slapping on “Toggle” won’t do it for me personally, and I’m glad I’m not alone in believing this makes a difference.

However… Creating a new DTD and adding your extra feature just to make it valid is a bit like buying a law book, adding “I may steal candy from small children” and doing that under the premise that it’s still legal according to my special altered book. Of course it’s less severe when it comes to validation, but the principle is the same.

If I truly “need that level of standards compliance”, I’d care too much that I’d actually stepped outside of the standard to think that making a custom DTD is anything but a cop-out. It doesn’t justify anything, and I think that the article would be much more honest without this suggestion in it - it’s already honest enough to recognize that it’s a completely fabricated addition which stands outside the standard.

(Just to clarify: I’m certainly not against people extending the standards (like with the canvas tag), but those people document what their implementation does, why it’s better than existing approaches (if any) and then they submit it for review by the browser community and possible inclusion in a future standard. This is, in comparison, a one-off kludge, serving only to aid one of many scripts.)

Don Wilson 30 Nov 05

To do a confirm popup, eg delete links, I do onclick=”return confirm(‘Message Here’);” which won’t send the pound sign up to the address bar if the person decides to cancel.

FAA 30 Nov 05

This reminds me of I wrote a couple of years ago, and it went something like this:


function toggle(div_to_toggle,copy_to_toggle,copy_open,copy_close) {
var found_div = document.getElementById(div_to_toggle);
var found_copy = document.getElementById(copy_to_toggle);
if (!found_div) return;
if (found_div.style.display.indexOf("none") >= 0) {
found_div.style.display = "";
found_copy.innerHTML = copy_open;
} else if (found_div.style.display == "") {
found_div.style.display = "none";
found_copy.innerHTML = copy_close;
}
}

<a href=”javascript://;” onclick=”toggle(‘div-id’,’copy-id’,’I am open!’,’I am closed!’)” id=”copy-id”>I am closed!</a>

One function, twelve lines (unless you want the nasty old non-DOM code which I’ve stripped out), and no prototype or custom attributes needed. Yes, it has the draw back of having to write your ‘I am closed!’ copy twice - but you were going to have that be dynamic and written out by your scripting language of choice anyway, weren’t you?

rick 30 Nov 05

Perhaps your link example is a bit too simple. It could easily be done by toggling the display of two links, or setting the onclick to: onclick=”Element.activate(‘active text’, ‘active_class’)”. What if you started using custom attributes to add more hooks into your HTML for your (or anyone else’s) javascript to modify the page greasemonkey-style?

A List Apart had an article on the idea of inserting custom attributes to validate your form fields: Validating a Custom DTD.

adam 30 Nov 05

One elegant solution i’ve used in the past, for things like paging where a stateful link is important is to link use a format like this:

then, if the user bookmarks the page, the state is maintained in the link and parsed by the app like a query string element.

This, of course, works best when the app is only one page big, but it’s nice.

adam 30 Nov 05

of course in the above comment, it should read:

<a href=”#page1” onClick=”goToPage(‘5’)” >

namelessmike 30 Nov 05

X = eXtensible. I’m as big of a semantic markup geek as the next guy, but the standards exist to serve us, not the other way around. Not to mention, implementing custom schemas is perfectly valid and is part of the BIG xml picture, a picture much broader, more future-ready, more exciting, and yes, even more semantically refined than the milk of baseline xhtml 1.x.

Rimantas 01 Dec 05

I’d vote for having only id attributes for those links all else leaving to JavaScript (or should I say ECMAScript?).
This will leave us with the only place for a changes - some .js file.
With custom DTD it all gets scattered acros DTD, (X)HTML and
JS. Where is the DRY and elegance in it?

Andrew Green 01 Dec 05

I was going to write that using a custom namespace for your own attributes in an XHTML document would be a much more standards-compliant way to go — but then I remembered that (a) you’d then have to serve the document with the correct MIME type, and (b) I don’t think innerHTML would work any more. I’d love to be proved wrong on both these points, as exploiting namespaces (the X in XHTML) strikes me as *so* much cleaner than writing custom doctypes or sending flagrantly invalid documents.

mark rush 01 Dec 05

FYI - googles recent Jagger update means inline css i.e. style=”display:none” gets trapped and lowers your position

good news is that if its in an external css file, it still works.

mark

Inge J�rgensen 01 Dec 05

another solution.. ( i hope my HTML works here )

Javascript:

function toggleLinkAndElement ( link, elementID, text ) {
Element.toggle( elementID ); // toggle the element
if ( link.innerHTML != text )
{
// change link text
link.oldInnerHTML = link.innerHTML;
link.innerHTML = text;
}
else
{
// change it back
link.innerHTML = link.oldInnerHTML;
}
}

HTML:
<a href=”edit.php” onclick=”toggleLinkAndElement ( this, ‘editBox’, ‘Done editing’ ); return false;”>Edit</a>
<div id=”editBox”>
.. edit box ..
</div>

Jordan 01 Dec 05

On the issue of custom attributes:

So long as they’re not used frivolously, I think custom attribuets are perfectly acceptable to use (particularly within dynamic applications). Rather than introduce presentation into content, they’re often doing the complete opposite, taking content out of the presentational (well, the functional) layer and putting it into the body of the document.

Take the example in the post: the active and inactive text is all content, it’s just contextual.

The line blurs a little when custom attributes are used in a way I’ve used them, on table, row and cell elements to store a database name, record id and field name, so that I can write a generic JS function that will dynamically query a table from a database without my having to hard code the table/record/field names into a query url or into the js function itself.

In this case it’s still content I feel, it’s just meta-content.

Bryan C 01 Dec 05

Cool technique. May be useful.

I don’t think there’s anything wrong with custom attributes if they’re used for something like this. Validation is like spell-checking. It’s a useful tool, but tools are just a means to an end and not the end itself. No one would claim that a letter to my mom can’t be valid English because her street name isn’t in Word’s default dictionary. Likewise, if the syntax is correct and the custom attributes don’t interfere with the function of the page then they’re not doing any harm. It just shifts the burden back to the author to make sure they’re doing it right.

Dylan 01 Dec 05

Let’s try that html again…

For example:
<a href=”#” onclick=”TextToggle(this)” id=”EditLink”><span id=”EditLinkOn” class=”toggleText”>Close</span><span id=”EditLinkOff” class=”toggleText”>Open</span></a>


Justin Johnson 01 Dec 05

Inge J�rgensen, I like your comment. And this article was quite useful

Leigh Klotz 01 Dec 05

Sorry, there’s no preview:

  <switch>
    <case id="doView">
      <trigger appearance="minimal">
	<label>Edit</label>
	<toggle case="doEdit" />
      </trigger>
      <output ref="age">
	<label>Age</label>
      </output>
    </case>
    <case id="doEdit">
      <trigger appearance="minimal">
	<label>View</label>
	<toggle case="doView" />
      </trigger>
      <input ref="age">
	<label>Age</label>
      </input>
    </case>
  </switch>

Rahul 01 Dec 05

Arguably, the solution posted is a good one for handling states. But comments are heading for a StopDesign/Mezzoblue style discussion of semantics. In which case:

If you’re going to state that hyperlinks are used for navigating to locations, then the “state” changing action shouldn’t be attached to a hyperlink unless it’s being used in relation to that hyperlink. For example, accessibility:

Edit ‘something’

The implication would be that, provided Javascript support, the edit process is handled by prototype & scriptaculous by putting an inline editor in place. Failing that, head to the actual edit page URL to do it directly instead of asynchronously.

But if you’re not going to worry about accessibilty issues (most 37signals apps don’t to any extreme degree), then why “misinterpret” the semantics of the hyperlink?

edit something

Works just as well, but is semantically meaningless. You may ask: “why should a state activator be semantically meaningless?” — because XHTML at this point is a language used to describe documents, not applications. If this were XUL, XAML, or some other markup specifically created for describing how interaction with elements in an application is to be handled, then we’re on the right track. However, since XHTML is meant to just describe information and its relevance to other information within the global context of web based documents, it’s out of place here. And from a usability point of view, using a span no longer requires you to fill the href attribute with something useless and potentially confusing like “javascript:void(0)”. “Copy link location”, anyone?

Ultimately, however, 37signals’ point of view seems to be “do what works”. Because “what works” usually leads to “getting stuff done” and “less” (code, work, writing long comments debating semantics on SVN). Which is a pretty valid argument, given the popularity of Backpack and Basecamp.

NB: I think it would have been wiser to suggest using something like wb:activetext/wb:activeclass, eg. include the xmlns, since that’s how it was done on Writeboard (hence the wb). Making a suggestion without it could lead to people applying it directly to their code and breaking W3C validity. Which isn’t a big deal in and of itself, but on the other hand, it wouldn’t take much to explain how namespaces work in a few lines as an addendum to the post. If you went to the trouble of doing it yourself, you may as well tell others about it too.

David Cheong 01 Dec 05

Inge J�rgensen - nice tip. I didn’t know you could do that.

Question Inge or anyone else out there, can you pretty much assign values to any arbitrary attribute on the link?

For example, Inge’s example could have been written as:

link.bob = “any value” or
link.myAttribute = “any other value”

If this is the case, then any link can potentially become a map of name/value pairs.

Does this have any browser compatibility issues?

Sam Barnum 05 Dec 05

How about using <span> tags inside the link instead of custom attributes? Some advantages:
* Doesn’t require nonstandard attributes
* Allows images or styled text in the link contents instead of just plain text.
* Puts the styling back in the CSS file, instead of javascript code.

No idea what the formatting will look like, but here’s a sketch. The span tags in the link are set to visible/invisible in the CSS file for the various combinations of active links and child elements. The ‘toggle’ javascript simply changes the className of the link and content box from ‘activeLink’ and ‘activeBox’ to ‘inactiveLink’ and ‘inactiveBox’, respectively, and lets the CSS take care of the rest. No custom attributes needed for customized link text.


<style type=”text/css”>
.inactiveLink .activeText, .activeLink .inactiveText, .inactiveBox {
display:none;
}
</style>
<script>
function toggleEdit() {
var editLink = document.getElementById(‘editLink’);
var editBox = document.getElementById(‘editBox’);
var isActivating = editLink.className == ‘inactiveLink’;
editLink.className = isActivating ? ‘activeLink’ : ‘inactiveLink’;
editBox.className = isActivating ? ‘activeBox’ : ‘inactiveBox’;
}
</script>

<a href=”#” id=”editLink” onclick=”toggleEdit()” class=”inactiveLink”> <span class=”inactiveText”>Edit</span>
<span class=”activeText”>I’m Done Editing
</a>
<div id=”editBox” class=”inactiveBox”>
EditBox contents…
</div>

Vann 05 Dec 05

Here is an easy way to do this without having to “break” any kinds of validation. _( I hope this formats correctly )_ . Just use some sort of convention, delimiters work very well for me, in the following example I use, I scan the document for anchor tags and look for anchors that have the delimiter “|” in them and also have a class name of “toggler”. This way, all I ever have to do is assign an anchor or whatever element with a class name using this convention: “toggler|Open|Cancel”. Here is the code, hope it is useful:


function load_behavior(){
anchors = document.getElementsByTagName('a');

for(a=0; a //for implementing the sliding toggler
anchor_array = anchors[a].className.split("|");
if (anchor_array.length >= 2){
switch (anchor_array[0]){

case "toggler":
anchors[a].onclick = function(){

//re-init since it is in a different context
anchors_array = this.className.split(“|”);
this.innerHTML = (this.innerHTML == anchors_array[1]) ? anchors_array[2] : anchors_array[1];
if ($(‘form_to_show’).style.display == “none”){
Effect.SlideDown(‘form_to_show’);
}else{
Effect.SlideUp(‘form_to_show’);
}
alert(‘done’);
}
break;

}
}



}
}

window.onload = load_behavior;

Inge J�rgensen 10 Dec 05

David Cheong: should work in any browser (i’ve tested MSIE, firefox, safari, opera and konqueror). you can add both attributes and functions to dom objects.