Spry's HTML Panel

This post is more than 2 years old.

Over the past few weeks, I've been taking a look at Spry's widgets, specifically those related to form items. I've been surprised by what I've found. I really wish I would have looked at them earlier. Today I looked at another widget, the HTML Panel. This one isn't form related, but is darn cool.

The HTML Panel is not a "panel" like what you get in ColdFusion 8. You can think of the panel as simply an item on your web page that can be loaded with other content. For example, imagine this div:

<div id="content"> Stuff will load here. </div>

Now imagine I want to load content into this div. By creating an HTML Panel widget out of the div, I can easily change the content. Let's look at a real example.

First off - like the other widgets, you need to use a CSS and JavaScript file:

<html>

<head> <script src="/spryjs/SpryHTMLPanel.js" language="javascript" type="text/javascript"></script> <link href="/sprycssSpryHTMLPanel.css" rel="stylesheet" type="text/css"> </head>

Next I'm going to create a menu. This is what I'll use to load content:

<h2>Products</h2>

<p> <b> <a onClick="panel.loadContent('apple.html'); return false">Apples</a> / <a onClick="panel.loadContent('banana.html'); return false">Bananas</a> / <a onClick="panel.loadContent('cherry.html'); return false">Cherries</a> </b> </p>

Don't worry about the JavaScript just yet. Now I'll create the area where content will load:

<div id="product"> <p> Please select a product. </p> </div>

The last thing I'll do is enable the HTML panel with a line of JavaScript. This is like every other widget I've covered so far:

<script type="text/javascript"> var panel = new Spry.Widget.HTMLPanel("product"); </script> </body> </html>

I simply create a new instance of the HTMLPanel, and point it to the ID of the item that will be replaceable. Ok, so now if you go back to the JavaScript you can see what I use to load content:

<a onClick="panel.loadContent('apple.html'); return false">Apples</a>

I just use the loadContent function of the panel object. I point it to the HTML to load, and that's it! You can see a live example of this here. View source to see the complete example.

So far so good - and easy as well. But wait - it gets a lot sexier. One of the things Spry tries to help out with is a progressive enhancement. That is a fancy way of saying "support non-JavaScript" browsers. One of the options you can use when loading widgets is to supply an ID. Spry will load the remote content, but only display the stuff within the specified ID. Why is that sexy? Consider this new example (note, I trimmed a bit of the HTML):

<h2>Fragment Test</h2>

<p> <b> <a href="f1.html" onClick="panel.loadContent(this.href,{id:'content'}); return false">Test One</a> / <a href="f2.html" onClick="panel.loadContent(this.href,{id:'content'}); return false">Test Two</a> </b> </p>

<div id="panel"> <p> Please select something! </p> </div>

<script type="text/javascript"> var panel = new Spry.Widget.HTMLPanel("panel"); </script>

As with the previous example, I've got a menu on top with a section in the middle that will be dynamic, but let's focus on one of the links:

<a href="f1.html" onClick="panel.loadContent(this.href,{id:'content'}); return false">Test One</a>

Note that I have a normal href. Then notice my onclick. First off - the URL for the onclick refers to the same URL defined in the tag itself. A little fancy self-referring which is nice if you ever change the URL. The second argument passed to the loadContent function is an object with one key/value pair. The ID attribute simply means, "Load the remote URL, but just show the stuff inside the content id." Let's look at f1.html:

<html>

<head> <title>F1</title> </head>

<body>

<h2>F1</h2>

<div id="content"> <p> This is the content for fragement page 1. </p> </div>

</body> </html>

As you can see - only one div has the ID of content. Now think about it. With one link you have 2 possible things going on:

  1. If the user doesn't have JavaScript, it loads up f1.html, and they get the complete page.
  2. If the user does have JavaScript, the remote page is loaded, but only the content area is displayed.

So in one simple link you have support for both JS enabled browser and browsers that have JS turned off (or search engines). As I said - darn sexy! You can see this in action here, and I recommend testing it with JS on and off to see it working.

Now one thing you can may not like about this is that even with the JS-enabled clicks, you are loading all the HTML, but it should still be faster for the end user as the browser won't have to load layout UI and stuff like that - just the content it needs. You can get around this easily enough in ColdFusion of course. Have your non-JS link to foo.cfm, and your Spry link to foo.cfm?slim=1, where the existence of the URL parameter tells your layout code to suppress any output.

Lastly - be sure to check out the complete docs for the HTML panel:

HTML Region Overview

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Jeffrey Price posted on 11/5/2007 at 8:54 AM

If you are gonna make it progressive, wouldn't you just want to go all the way and make it unobtrusive as well by using Spry.Utils.addEventListener to add a click event to the a hrefs in question instead of using that nasty onclick event?

Just my $0.02 worth :)

Comment 2 by Raymond Camden posted on 11/5/2007 at 9:11 AM

Hmm, but if all we care about is js/no js, this is more than enough, right? It is my understanding, and I _definitely_ could be wrong, that what you propose is for more _elegant_ reasons than anything else - but definitely let me know if I'm wrong.

Comment 3 by levan posted on 11/5/2007 at 10:42 AM

spry is very promising framework, I have tested it with YUI and so far no problems that gives even more power(the only one thing that put me against the wall is that it does not support nested regions yet ) and all that w3C evangelists complaining about spry is not comparable with etc .... IMHO is just plain rant, w3C is a formality anyways.
also spry is bridging with Flex pretty well so we probably will see some integrations poping up soon .

Comment 4 by Jeffrey Price posted on 11/5/2007 at 4:37 PM

Beauty is indeed in the eye of the beholder.

The way I think of unobtrusive javascript is that it is separation of your markup from your application behavior, or your view from your model-controller. It is a good idea in our server side programming and it's also a good for client side programming.

All of your other Spry examples are done with unobtrusive javascript. If you'll notice, all the JS code was in one place and there was nothing but markup in your HTML section!

Comment 5 by Raymond Camden posted on 11/5/2007 at 5:11 PM

@Jeffrey: Point taken!

@Levan: Nested regions... do you mean nested data? If so - Spry supports that, and Spry _does_ validate now if I remember right.

Comment 6 by Kin Blas posted on 11/5/2007 at 10:16 PM

Just an FYI, an unobtrusive equivalent to Ray's sample can be found here on the Adobe Labs site:

http://labs.adobe.com/techn...

In particular, look at:

http://labs.adobe.com/techn...

That sample shows both the loading of pure HTML fragments, and the extraction of an HTML fragment from a static HTML page.

Comment 7 by Raymond Camden posted on 11/5/2007 at 10:20 PM

Hey Kin - is the movement of the event handler out also consider part of being unobtrusive? The example Spry doc did it as I did - so if both things need to be done then I'd update that doc as well.

Comment 8 by Kin Blas posted on 11/6/2007 at 12:21 AM

Hey Ray - Yes, unobtrusive JS means you move *all* script, including script in on* attributes, into an external file. The only <script> tags allowed are those that include external files.

Unobtrusive JS is a great concept that is gaining popularity, but it does have some issues folks should be aware of:

- It sometimes makes it harder to figure out how things work, since all of the behavior is moved into code, so you can't see the relationship between the tag and the event/behavior in the same place.

- Depending on how you unobtrusively attach behaviors, and the number of elements you are attaching behaviors to, there may be a performance penalty you pay. If you have ID attributes on everything that you will attach behaviors to, it may not be that bad, but if you are using CSS class or contextual selectors (ala Spry's Element Selector, JQuery, DOMQuery, etc) there is a penalty for traversing the *entire* document to find those elements.

The first issue is something we wrestle with internally. Our samples tend to be "obtrusive" so the user reading our docs and looking at our samples can understand it right away ... but we're trending towards providing samples that do it both ways so folks can wrap their heads around how things work, and how to make it unobtrusive if they want/need to.

Comment 9 by Raymond Camden posted on 11/6/2007 at 12:24 AM

So can you explain why someone would do this? I get why someone would handle both js/non js browsers. But why, both as a developer and an end user, would I want to go to the point of having no JS except for external script files?

Comment 10 by Kin Blas posted on 11/6/2007 at 12:35 AM

Some of the "benefits", taken from this doc:

http://labs.adobe.com/techn...

include:

* The ability to make incremental modifications to the HTML markup structure or the behavior code independently without having to modify the other.

* Because the behavior implementation is externalized, it can be shared across multiple HTML pages, so the bandwidth necessary to view these pages is reduced since the files related to the behaviors are downloaded and cached by the browser once. This also results in smaller HTML pages since the behavior code is not duplicated within the actual markup itself.

* Since the HTML markup is smaller and semantic, it is also easier to read which aids accessibility with screen readers, search engine web crawlers, and browsers or other user agents that don't necessarily support the behaviors you've implemented.

Comment 11 by Raymond Camden posted on 11/6/2007 at 12:43 AM

Thanks for taking the time, Kin. It makes sense to me now. Not quite sure if it is worth _all_ the effort, but it definitely make sense.

Comment 12 by Andrea posted on 11/7/2007 at 12:28 AM

I think anything must be well mixed. I understand ( and I make this any day ) the point to keep html and js separated ( I mean do not use onclick etc...) but I really find uncomfortable to use only js in external file.
I use jquery a lot but craeting the content dinamically most of my js attaching event is inserted in the html template inside script tags....I do not think the purpose of a good ceveloper should be come back to hardcoding....
What I did not like of some js framework is the use of non valid attributes to attach event and I remember in the beginning spry was bad right under this point.

Comment 13 by Cyrill posted on 11/16/2007 at 12:22 AM

Can this panel be a CFM file so that we can pass an url variable and do some server side processing before loading the page?

Comment 14 by Raymond Camden posted on 11/16/2007 at 12:31 AM

Sure, you can point to ANY file. CFM, ASP, even PHP. It's all HTTP requests.

Comment 15 by Cyrill posted on 11/16/2007 at 1:03 AM

Very nice indeed, I am playing with it now. My first attempt was to check if spry validation on a form would work, and I can't seem to make it work:

<a href="testform.cfm" onclick="panel.loadContent(this.href,{id:'panelform'}); return false">Click</a>
<div id="panel1">Please click</div>
<script type="text/javascript">
var panel = new Spry.Widget.HTMLPanel("panel1");
</script>

loads the div just fine:

<div id="panelform">
<form name="form" enctype="multipart/form-data" action="">
<span id="sprytextfield1">
<label>
<input type="text" name="text1" id="text1" />
</label>
<span class="textfieldRequiredMsg">A value is required.</span></span>
<input name="submit" type="submit" value="submit" />
</form>
<script type="text/javascript">
<!--
var sprytextfield1 = new Spry.Widget.ValidationTextField("sprytextfield1");
//-->
</script>
</div>

However, just like when it is placed inside a <cfdiv> spry validation is ignored (although it partially does work, since the <span class="textfieldRequiredMsg">A value is required.</span> does not display on load, meaning JS works initially.

Comment 16 by Raymond Camden posted on 11/16/2007 at 2:08 AM

First off - your file upload may not work well in there.

So one reason your validation may not work is that you aren't including the CSS/JS. When you loaded the content, you said, 'just user stuff in id:panelform. Well that didn't include the CSS/JS for validation.

Comment 17 by Cyrill posted on 11/16/2007 at 3:07 AM

Ray,

I did test file upload from a form inside cfdiv. It seems to work when it is not cfform. I have not tried uploading files from spry panels.

As for validation, I load both CSS and js for spry validation in the main template that contains the panel. When js file is missing, then the "value is required" span shows up on load, that suggests to me that js is loaded. and firebug lists the js file as well as CSS styles as available.

There is always a possibility I am doing something wrong or misspelling - I never underestimate my ability to chase wild geese while there is a cooked one sitting in the oven.

Comment 18 by Raymond Camden posted on 11/16/2007 at 7:54 PM

Hmmm. I'm not sure then. Don't forget that Spry has a forums over on labs. Try there and see if you get answer. If you do - please share it here! It does seem like it _should_ work.

As a random though, in your JS block, before you make the widget, do this for me:

alert('hi')

When the content loads, do you see the alert? Just because you don't see the span doesn't mean that the JS is working - cuz the CSS file should hide it instead. (AFAIK)

Comment 19 by Raymond Camden posted on 11/21/2007 at 10:48 PM

Cyrill, on the next blog post in this series, a user named Rual noticed there is a evalScripts argument you can pass to the constructor for the HTML widget. Try that. I think thats the solution.