29 November 2007
Beginning
The Spry periodic table takes advantage of many Spry techniques that the Spry community might be interested in. This document steps through the code section by section and explains the thinking and methods behind the code. Since this demo gets away from our common use patterns, it may serve to expand the ways in which Spry can be used on pages.
You can explore a working version of the Spry periodic table on Adobe Labs.
The Spry Periodic Table uses an HTML data set to populate the periodic table. The actual data source table is on the same page. This allows the page to load faster, allows the page to degrade gracefully, and gives search engines content to index. HTML tables are easier to create and edit than XML and you can use visual editors to modify them.
We then use Spry:repeats and Spry:regions, plus a good use of CSS classes to position things so they look like a periodic table. These classes are also used to highlight the different sections of the table via the links.
The tooltip widget is used with a detailregion to provide additional information when mousing over an element.
The data table, located at the bottom of the page, is an HTML table:
| No | Atomicweight | Name | Symbol | MP | BP | Density |
|---|---|---|---|---|---|---|
| 1 | 1.0079 | Hydrogen | H | -259 | -253 | 0.09 |
| 2 | 4.0026 | Helium | He | -272 | -269 |
The header row is used as the data reference names used throughout the page. We use No (atomic number) frequently as a unique ID. We also use Group, Period, and Element_Type for additional functionality, explained later.
Using an HTML table made it easy to add columns for additional data points and to find elements quickly.
As with all Spry pages, you need to attach files so you can get started.
<link href="SpryAssets/atom.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="SpryAssets//SpryData.js"></script>
<script type="text/javascript" src="SpryAssets//SpryHTMLDataSet.js"></script>
<script type="text/javascript" src="SpryAssets//SpryTooltip.js"></script>
<script type="text/javascript" src="SpryAssets/atom_functions.js"></script>
A big part of the functionality depends on class names. Open the atom.css file.
You see a set of class names that are named after the different Element_Type references. This allows us to color the different element types using a data reference, since the value of the data reference and the class name are identical. Other classes are used for positioning things correctly within the element div. (The element div is the div tag that contains the data for a single element. )
The Periodic Table uses four data sets:
<script type="text/javascript" language="javascript">
var ds1 = new Spry.Data.HTMLDataSet(null,"atoms");
var types = new Spry.Data.HTMLDataSet(null,"atoms",{distinctOnLoad:true, distinctFieldsOnLoad:['Element_Type']});
var periods = new Spry.Data.HTMLDataSet(null,"atoms",{distinctOnLoad:true, distinctFieldsOnLoad:['Period']});
var groups = new Spry.Data.HTMLDataSet(null,"atoms",{distinctOnLoad:true, distinctFieldsOnLoad:['Group'],sortOnLoad:"Group",sortOrderOnLoad:"ascending"});
groups.setColumnType("Group","number");
...
</script>
var ds1 = new Spry.Data.HTMLDataSet(null,"atoms"); atoms is the ID of the data table.var types = new Spry.Data.HTMLDataSet(null,"atoms",{distinctOnLoad:true, distinctFieldsOnLoad:['Element_Type']}); distinctOnLoad allows us to only get an Element type once. distinctFieldsOnLoad tells Spry to get every distinct value in the Element_Type column.var periods = new Spry.Data.HTMLDataSet(null,"atoms",{distinctOnLoad:true, distinctFieldsOnLoad:['Period']});var groups = new Spry.Data.HTMLDataSet(null,"atoms",{distinctOnLoad:true, distinctFieldsOnLoad:['Group'],sortOnLoad:"Group", sortOrderOnLoad:"ascending"});groups.setColumnType("Group","number");, which tells Spry that these are numbers. Without this line of code, Spry would treat them as text and may not order them correctly. The sortOnLoad and sortOrderOnLoad ensures that they display in the numeric order. If we didn't set the sort, they would display in the order they appear in the document.There is one more function in the <script>:
ds1.addObserver({onPostLoad: function(ds, data) {
Spry.Utils.removeClassName("mainRegion", "SpryHiddenClass");
}});
The CSS class SpryHiddenClass only sets display:none; This hides and removes the element from the flow of the page.
The mainRegion div wraps the entire periodic table (but not the data table) within it. It has the SpryHiddenClass class applied to it. This is used for two reasons:
onPostLoad notification and then runs the Spry.Utils.removeClassName. This removes SpryHiddenClass from mainRegion. This will cause the periodic table to show, all ready to go.SpryHiddenClass applied to it and Spry will not remove it, since JavaScript is turned off. This allows the data table to be displayed instead. Because the HTML data set is using a table on the same page, Spry will hide the data table automatically.The HTML markup for the table is short, but contains a lot of logic for both layout and filtering. I will start with the main block of the periodic table and then explain the filters.
The bulk of the periodic table is a single div, which I refer to as the element div since it contains the actual chemical information.
Using Spry, this one DIV is repeated for every element in the data table. The rest of the work is simply positioning those element divs correctly, so it looks like a real periodic table.
This element div has many classes applied to it. This allows us to identify each element and to wrap lines correctly as well as to filter easily.
<div tabindex="{No}" id="div_{No}" class="elementBlock {Element_Type} {Period} {Name} {Group}"
spry:if="({No} < 58 || ({No} > 71 && {No} < 90) || {No} > 103)" onmouseover="ds1.setCurrentRow('{ds_RowID}');" spry:hover="hover">
<span class="number">{No}</span> <span class="group"> {Group} </span>
<p align="center">{Symbol}</p>
<span class="weight">{Atomicweight}</span> </div>
</div>
The bulk of the code above is the actual content: The <span> tags and the <p> contain the Spry data references for Atomic Number {No}, Group Number {Group}, the element symbol {Symbol}, and the atomic weight {Atomicweight}. The CSS classes number, group, and weight are defined in atom.css to position these data references within the element div.
The logic for the element div is in the attributes of the div tag.
tabIndex="{No}" is used to allow tabbing between elements for accessibility reasons. This gives the tabindex a unique number {No} to allow tabbing in atomic order.id="div_{no}" provides a unique ID for each of the element divs. These are used in the filter functions to gray out the other elements.class="elementBlock {Element_Type} {Period} {Name} {Group}"elementBlock is defined in atom.css. It sets the size of the element div, the border, padding, and font size. Key here is the float:left that lines up the divs horizontally.{Element_Type} are all defined in atoms.css. The data reference that adds the class name that sets the background colors you see on the table.{Period} and {Group} are added so we can filter on them. They are not defined in atoms.css. They are only used for logic in filtering the periods and groups.{Name} are used so we can define classes for specific elements. For example, we use specific classes like .Hydrogen to great the gap between it and Helium correctly.onmouseover="ds1.setCurrentRow('{ds_RowID}');" is used for the tooltip to update the tooltip spry:detailregion. This ensures that the tooltip always shows details for the current element.spry:hover="hover" sets the hover class for changing the text color when mousing over an element.The spry:if takes some explanation. The two blue rows at the bottom are special. They are the Lanthanide and Actinide series. They are always displayed in separate columns below the main table, but numerically, they are out of order. Note that they have a Number (upper left corner) of 58–71 and 90–103. Note that on Periods six and seven (click the Periods filter to highlight them), the number jumps from 57 straight to 72, and from 89 to 104.
So we have to logically not display those in the main table, and ensure they only show up below. (This separation is the only reason we have to duplicate code in this demo!)
The spry:if says:
spry:if="({No} < 58 || ({No} > 71 && {No} < 90) || {No} > 103)"
This is the logic that says: if the Number is less than 58 or greater than 71 and less than 90 but greater than 103, print it here. Note the ( ) that surround the highlighted code. This separates the logic so everything works correctly. You will see in the other element DIV block that an inverse condition displays the other two rows.
For the Lanthanide block, the two blue rows at the bottom, twe need to display the elements that are not displayed above.
We simply repeat the markup from above, with a new spry:region and spry:repeatchildren. The only difference in this block is that spry:if is the reverse of the one above:
spry:if="({No} >= 58 && {No} <= 71) || ({No} >= 90 && {No} <= 103)"
Other than that, the two spry:regions that output the elements are the same. The Lanthanide div has CSS that positions it left so it lines up vertically correctly. The <br style="clear:both;" /> between the two element blocks puts the Lanthanide block on a new line.
The mainRegion div wraps the entire periodic table. It is used as the main spry:region and is used for CSS purposes. The <br> is a critical component of the layout.
<div spry:region="ds1" spry:repeatchildren="ds1" class="SpryHiddenRegion" id="mainRegion">
<br spry:if="({No} == 3) || ({No} == 11) || ({No} == 19) || ({No} == 37) || ({No} == 55) || ({No} == 87)" style="clear:both;" />
This DIV is the spry:region for ds1, the main data set. We also set a spry:repeatchildren on the data set. This is the attribute that causes all the element divs and the breaks ( <br>) to repeat. The SpryHiddenRegion class is used to hide the div while the page loads. The ID is needed because we use it on the observer described in the Data sets section above.
So this code repeats the element div, which since it is float:left:, will line up all the elements in a long horizontal line. In order to make the table look correct, we need to start a new line at precise times, Helium, Neon, and so on. Every time we hit a green element, we need to start a new line. We know the element Numbers where this needs to happen.
The <br> has a spry:if that has a "clear:both;". This clears the float and causes the remaining content to wrap to the next line.
We use the spry:if to determine when to add a <br> that will cause this break. So we write a statement that says, "Break when the number {No} equals 3 or 19 or 37 or 55 or 87". Note that we actually use the number that is the first element of the new row.
This break, along with the float on the .elementBlock class, are the key components of getting the layout correct. If we just had that, it would have the correct rows, but we still need to create that gap in the first three rows (Periods, hence the name periodic table), so the elements line up correctly vertically. To do this, we take advantage of having the {Name} class on every element div.
In any periodic table, the first three rows (Periods) have a gap between elements because they need to line up vertically in the correct column (Group). To do this , we create classes that use a specific element name and contain simple CSS.
To position these three rows correctly, we simply add a large margin to the correct elements. This pushes the elements to the right so they line up right. The float ensures it stays in line horizontally.
The atoms.css file contains classes called .Hydrogen, .Beryllium, and .Magnesium. (Note that they are uppercase because CSS and JavaScript are case sensitive.). The code is:
.Hydrogen {
margin-right:928px;
}
.Beryllium, .Magnesium {
margin-right:580px;
}
This pushes those rows over correctly. This, and a couple other details, are why we pass the element name as a class name.
We use a Spry Tooltip widget to show additional information about the element. The table has much more information in it than we can fit in the element div.
The Tooltip widget works by having a tooltip element, which contains the tooltip content, and a trigger, which activates the tooltip. Since we want the data to update for each element, we use a spry:detailregion for this. This is tied to the onmouseover="ds1.setCurrentRow('{ds_RowID}');" attribute on the element divs. This causes the detailregion to update on the onmouseover event.
Because the tooltip widget is a detailregion, we need to take steps to make sure it functions properly. Generally, with widgets and Spry data, we have to reactivate widgets that get wiped out when the data refreshes. In this case, we use observers to trigger the widget constructor after the detail region updates.
<div spry:detailregion="ds1" id="tooltip" class="SpryHiddenRegion"> Name:<strong>{Name}</strong><br />
Type:<strong>{Element_Type}</strong><br />
Group:<strong>{Group}</strong><br />
Symbol:<strong>{Symbol}</strong><br />
Period:<strong>{Period}</strong><br />
Boiling Point:<strong>{BP}</strong><br />
Melting Point:<strong>{MP}</strong><br />
Density:<strong>{Density}</strong><br />
Atomic Weight:<strong>{Atomicweight}</strong><br />
Electron Config:<strong>{Electron_configuration}</strong><br />
Year Discovered:<strong>{DiscoveryYear}</strong> </div>
<script type="text/javascript">
Spry.Data.Region.addObserver('mainRegion',{onPostUpdate:function(){var tt1 = new Spry.Widget.Tooltip('tooltip','.elementBlock',{showDelay:750, hideDelay:500});}});
Spry.Data.Region.addObserver('Lanthanide',{onPostUpdate:function(){var tt2 = new Spry.Widget.Tooltip('tooltip','.elementBlock',{showDelay:750, hideDelay:500});}});
</script>
</div>
The div is a detailregion tied to ds1, the main data set.
The id="tooltip" is used to identify the tooltip content for the widget.
The SpryHiddenRegion class is used to automatically hide the markup as the page loads. It is removed when Spry is done loading the page.
The <script> tags are within the detailregion. Spry has code that finds script tags within regions and executes them after the data is finished loading.
The highlighted regions are observers that recreate the tooltip every time the detail region updates. We add an observer to elements by their ID. In this case, we attach observers to the two spry:regions: mainRegion and Lanthanide. The onPostUpdate notification runs the function() after the data is finished updating in the region.
The function is the actual Tooltip constructor. tooltip is the ID of the tooltip content: the detailregion.
.elementBlock is the class name that defines the trigger. Every time someone mouses over an element with a class of elementBlock, the tooltip appears. In this case, the elementBlock class is attached to every element div. Recall that is the class that sets the size and border? So now the tooltip will trigger every time you mouse over the element div. The onmouseover attribute causes the data to update to the current element information and the observer instantly creates the new tooltip.
At the top of the page, we have the links that highlight the particular part of the table. We can select element types, groups, or periods. They are highlighted by graying out the elements that don't belong to the selection.
We use Spry to output the text and simple custom functions to enable this inverse highlighting. These two functions are in atom_functions.js. One function does the highlighting. The other clears any current highlighting. The hasClassName is a utility function that will be included in the next version of Spry.
function GrayOutTable(activeClass)
{
var rows = ds1.getData(true);
var numRows = rows.length;
for (var i = 1; i <= numRows; i++)
{
var ele = Spry.$("div_" + i);
if (ele)
{
if (!Spry.Utils.hasClassName(ele, activeClass))
Spry.Utils.addClassName(ele,"grayOut");
else
Spry.Utils.removeClassName(ele,"grayOut");
}
}
}
There is a class defined in atom.css called grayOut that changes the opacity of an element to 30%.
We pass in the name of the class that we want to highlight. We loop through the data set and apply the grayOut class to all those element divs that do not have the class name that is passed in. We use the id="ds_{No}" that we set up on the element divs to identify the divs that need this new class.
This function gets the data in ds1 and from that we get the number of rows (numRows).
We then loop through all the element divs. We use Spry.Utils to find out if the element does not have the class name we are looking to highlight. If it does not have it, we add the grayOut class. If it does have the class name we are looking for, we leave it alone. That's the inverse highlighting. If it already has the grayOut class applied, we assume it is from a previous highlight and remove it.
The other function clears any highlighting by looping through the element divs and removes any instance of the grayOut class.
To use these functions, we use the other data sets. All three work the same way. For example:
<div>
<span class="redText">Element Types: </span> <span spry:region="types" spry:repeat="types" class="SpryHiddenRegion"> <a href="#" onclick="GrayOutTable('{Element_Type}');">{Element_Type}</a> </span></div>
<span> simply adds the redText class to the initial text.<span> defines the spry:region for, in this case, the types data set. The spry:repeat is tied to the same data set. The SpryHiddenRegion hides the markup until the page is ready.<a> has a null href so we get the hand cursor. The onclick attribute is the actual function. The parameter in the function is the Element_Type data reference and it is the value we are sending to the function. We want to highlight that Element Type. The actual text of the link is also the Element Type. The space ( ) is used to separate the links when we repeat through the data set.So this will loop through the data set, which has the distinct Element Types and attaches the function to each type. This easily creates the list of highlight types.
We use the same code for the groups and periods data sets. They all go to the same function, since we are attaching those values on the element div as class names. So we can use these values to pick the right elements to highlight.
This is a nice example of how to use Spry for more complicated web layouts. With a small amount of markup, some CSS and a couple custom functions, you have a functional, interactive learning tool.
Hopefully, you have found an interesting technique here or expanded your ideas of how to use Spry.
Tutorials and samples |
| 04/23/2012 | Resolution/Compatibility/liquid layout |
|---|---|
| 04/20/2012 | using local/testing server with cs5 inserting images look fine in the split screen but do not show |
| 04/18/2012 | Ap Div help |
| 04/23/2012 | Updating |