//#############################################################################
//
// A JavaScript library for generic HTML rendering
//
// ©2007 Peter A. Kemmer
//
// Library assumptions:
//
// - Attributes are stripped from the end of html related functions using
// the Atrribute constructor and the argument count as a starting index
//
// - A certian amount of attribute name mangling has to occur to make all
// the possible tag attributes safe for the JavaScript namespace... and
// you could still totally mess things up using certain attribute names
//
// - 'clazz' is used all over the place as a name replacement for 'class'
//
// - Styles are handled differently from most attributes. They collect as
// as you add them; old vaues aren't replaced, they have the new values
// tacked on to them, after a space. It 'cascades' styles just like CSS
//
// setAtribute("class", "foo");
// setAtribute("class", "bar");
// setAtribute("style", "width: 100px;", "style", "height: 100px;");
//
// toString() == 'class="foo bar" style="width: 100px; height: 100px;"'
//
// - If any normal tag doesn't take 'href' as a parameter but you set the
// value in the attributes, it will automatically nest in an anchor tag
//
// - Attribute objects can NOT be Arrays... using an array for attributes
// will cause the array's values, not properties, to be copied at times
//
// - 'Smart' HTML objects always override toString() to render their html
//
//#############################################################################
//-----------------------------------------------------------------------------
// Verify Dependencies
//-----------------------------------------------------------------------------
if (!isDefined(CORE_LIBRARY_LOADED)) {
alert("Error: The library Core.js needs to be loaded before HTML.js");
}
//-----------------------------------------------------------------------------
// Constants
//-----------------------------------------------------------------------------
var HTML_LIBRARY_LOADED = true;
// A map of tag types that normally use the href attribute. When other tag
// types define an href attribute, it gets removed from the attributes and
// the tag is wrapped in an anchor instead, linked to the value of href!!!
var TAGS_THAT_USE_HREF = new Object();
TAGS_THAT_USE_HREF.a = true;
TAGS_THAT_USE_HREF.link = true;
// A map of properties that Attribures.toString shouldn't render directly!
var HIDDEN_ATTRIBUTES_PROPERTIES = new Object();
HIDDEN_ATTRIBUTES_PROPERTIES.setAttribute = true;
HIDDEN_ATTRIBUTES_PROPERTIES.emptyClass = true;
HIDDEN_ATTRIBUTES_PROPERTIES.emptyStyle = true;
HIDDEN_ATTRIBUTES_PROPERTIES.copyClassAndStyleTo = true;
HIDDEN_ATTRIBUTES_PROPERTIES.copyAttributes = true;
HIDDEN_ATTRIBUTES_PROPERTIES.toString = true;
HIDDEN_ATTRIBUTES_PROPERTIES.clazz = true;
HIDDEN_ATTRIBUTES_PROPERTIES.clazzBuffer = true;
HIDDEN_ATTRIBUTES_PROPERTIES.style = true;
HIDDEN_ATTRIBUTES_PROPERTIES.styleBuffer = true;
//#############################################################################
// Comments
//#############################################################################
/**
* getConditionalComment
*
* Get the html for a conditional comment
*
* @param condition - The condition
* @param comment - The comment
*/
function getConditionalComment(condition, comment) {
var buffer = new StringBuffer();
buffer.append("<!--[if ", condition, "]>\n", comment.toString().prependLines(), "\n<![endif]-->");
return buffer.toString();
}
/**
* writeConditionalComment
*
* Write the html for a conditional comment
*
* @param condition - The condition
* @param comment - The comment
*/
function writeConditionalComment(condition, comment) {
write(getConditionalComment(condition, comment));
}
//#############################################################################
// Attributes - HTML tag attribute management
//#############################################################################
/**
* Attributes 'Constructor'
*
* A convenience function to 'construct' an attributes object, in two ways
*
* If the function detects the first argument is an array, it does this...
*
* ARGUMENT PARSING MODE:
*
* @param <argument 1> - Array of arguments from another method
* @param <argument 2> - Optional index to start parsing at
* @param <argument 3> - Optional object containing default attributes
*
* Parse an argument array, starting at an optional index, and convert any
* objects or data into a new object containing those values as properties
*
* If an argument is an object we will copy all of the object's properties
*
* If an argument is a primitive we will use it and the next argument as a
* name/value pair, converting the name to a property containing the value
*
* Additionally, you can pass an object containing default attributes that
* will only be set if the same attributes were not found parsing the args
*
* Otherwise it defaults to...
*
* RAW ATTRIBUTES MODE:
*
* @param <object> || <name, value> ...
*
* Get the argument array and stuff it into a new Attributes object, using
* that one to do the argument parsing mode, then copy all it's attributes
*/
function Attributes(firstArgument) {
// class variables --------------------------------------------------------
this.clazzBuffer = new StringBuffer(" ");
this.styleBuffer = new StringBuffer(" ");
// construct class --------------------------------------------------------
if (arguments.length >= 1) {
if (isArray(firstArgument)) {
// Assume that we are running argument parsing mode
// which is used to convert function arguments into
// attributes using a argument array & start index!
// You can also pass in a default attributes object
// in this parsing mode, copying only unset values!
var myArguments = arguments[0];
var startIndex = arguments[1] ? arguments[1] : 0;
var defaultAttributes = arguments[2];
// Iterate through myArguments, converting to props
for (var ii = startIndex; ii < myArguments.length; ii++) {
var argument = myArguments[ii];
if (typeof argument == "object") {
// Copy attributes from object to this one!
this.copyAttributes(argument);
} else {
this.setAttribute(argument, myArguments[ii + 1]);
// Skip right past the one we just utilized
ii++;
}
}
// Only use default values if they haven't been set
if (defaultAttributes != null) {
this.copyAttributes(defaultAttributes, false);
}
} else {
// If the first argument ISN'T an array, just shove
// every last argument into a NEW Attributes object
// as an argument array (triggering the above) then
// copy the attributes from the new object to here!
// It probably means we're parsing name/value pairs
var attributes = new Attributes(arguments);
this.copyAttributes(attributes);
}
}
}
/**
* Attributes.setAttribute
*
* Set an attribute on this object, overwriting old values by default
*
* @param attribute - The attribute to set
* @param value - The value to set
* @param overwriteOpt - Replace if attribute already exists, defaults true
*/
Attributes.prototype.setAttribute = function(attribute, value, overwriteOpt) {
var overwrite = overwriteOpt ? overwriteOpt : true;
// Can't use this[property] syntax, use a proxy!!!
var proxy = this;
// Always CONCATENTATE styles, they're cumulative!
// Also, trim off the whitespace before adding it!
attribute = sanitizeAttribute(attribute);
if (attribute == "clazz") {
this.clazzBuffer.append(value.trim());
} else if (attribute == "style") {
value = value.trim();
if (!value.endsWith(";")) {
// Override the default item separator " "
this.styleBuffer.appendSep("; ", value);
} else {
this.styleBuffer.append(value);
}
} else if (overwrite || proxy[attribute] == null) {
proxy[attribute] = value;
}
}
/**
* Attributes.emptyClass
*
* Removes any classes in the class buffer
*/
Attributes.prototype.emptyClass = function() {
this.clazzBuffer.empty();
}
/**
* Attributes.emptyStyle
*
* Removes any styles in the style buffer
*/
Attributes.prototype.emptyStyle = function() {
this.styleBuffer.empty();
}
/**
* Attributes.copyClassAndStyleTo
*
* Copy this object's class and style into the provided buffers
*
* @param clazzBuffer - The class buffer to copy into
* @param styleBuffer - The style buffer to copy into
*/
Attributes.prototype.copyClassAndStyleTo = function(clazzBuffer, styleBuffer) {
if (!this.clazzBuffer.isEmpty()) {
clazzBuffer.append(this.clazzBuffer.getActualData());
}
if (!this.styleBuffer.isEmpty()) {
styleBuffer.append(this.styleBuffer.getActualData());
}
}
/**
* Attributes.copyAttributes
*
* Copy an object's attributes to this one, overwriting old values by default
*
* @param source - The source object
* @param overwriteOpt - Replace if attribute already exists, defaults true
*/
Attributes.prototype.copyAttributes = function(source, overwriteOpt) {
var overwrite = overwriteOpt ? overwriteOpt : true;
// Copy the contents of the class and style buffers
if (isDefined(source.copyClassAndStyleTo)) {
source.copyClassAndStyleTo(this.clazzBuffer, this.styleBuffer);
}
for (var attribute in source) {
// Wee hacky way to remove function properties!
if (!isValidAttribute(attribute)) {
continue;
}
this.setAttribute(attribute, source[attribute], overwrite);
}
}
/**
* Attributes.toString
*
* Gets the HTML used to render an Attributes object
*/
Attributes.prototype.toString = function() {
var buffer = new StringBuffer(" ");
// Special case the 'class' and 'style' attributes
// Copy hand set attributes to the correct buffer!
if (isDefined(this.clazz)) {
this.setAttribute("class", this.clazz);
delete this.clazz;
}
if (isDefined(this.style)) {
this.setAttribute("style", this.style);
delete this.style;
}
// Convert buffers to strings and tack on to start
if (!this.clazzBuffer.isEmpty()) {
buffer.append('class="');
// Need to override the default separator " "!
buffer.appendSep("", this.clazzBuffer.toString(), '"');
}
if (!this.styleBuffer.isEmpty()) {
buffer.append('style="');
// Need to override the default separator " "!
buffer.appendSep("", this.styleBuffer.toString(), '"');
}
// Can't use this[property] syntax, so use a proxy
var proxy = this;
for (var attribute in proxy) {
// Wee hacky way to remove function properties
if (!isValidAttribute(attribute)) {
continue;
}
// De-sanitize the attribute name before using
buffer.append(restoreAttribute(attribute));
if (proxy[attribute] != null) {
// Must override the default separator " "
buffer.appendSep("", '="', proxy[attribute], '"');
}
}
return buffer.toString();
}
//-----------------------------------------------------------------------------
// Attribute Utilities
//-----------------------------------------------------------------------------
/**
* isValidAttribute
*
* Dumb hack to screen out the Attribute object properties that
* point to functions instead of attributes we want to display!
*
* @param attribute - The name of the attribute
*/
function isValidAttribute(attribute) {
// Watch me go out of sync when someone edits the functions
return !isDefined(HIDDEN_ATTRIBUTES_PROPERTIES[attribute]);
}
/**
* sanitizeAttribute
*
* Convenience routine to sanitize attributes, allowing direct access to
* them as properties off of an object by removing any namespace issues!
*
* @param attribute - Attribute to sanitize
*/
function sanitizeAttribute(attribute) {
attribute = attribute.trim().toLowerCase();
if (attribute == "class") {
return "clazz";
}
// Exit before a big loop of doom
// if it shouldn't even bother!!!
if (attribute.indexOf("-") == -1) {
return attribute;
}
// Convert "foo-bar" to "fooBar"
// I bet there's regex to do it!
var buffer = new StringBuffer();
for (var ii = 0; ii < attribute.length; ii++) {
if (attribute.charAt(ii) == "-") {
buffer.append(attribute.charAt(ii + 1).toUpperCase());
ii++;
} else {
buffer.append(attribute.charAt(ii));
}
}
return buffer.toString();
}
/**
* restoreAttribute
*
* A convenience routine to restore attributes allowing direct access to
* them as properties off of an object by removing any namespace issues!
*
* @param attribute - Attribute to sanitize
*/
function restoreAttribute(attribute) {
if (attribute == "clazz") {
return "class";
}
// Convert "fooBar" to "foo-bar"
// I bet there's regex to do it!
var buffer = new StringBuffer();
for (var ii = 0; ii < attribute.length; ii++) {
var charAt = attribute.charAt(ii);
if (charAt >= "A" && charAt <= "Z") {
buffer.append("-", charAt.toLowerCase());
} else {
buffer.append(charAt);
}
}
return buffer.toString();
}
//#############################################################################
// HTML Tags
//#############################################################################
//-----------------------------------------------------------------------------
// Generic HTML Tag Rendering
//-----------------------------------------------------------------------------
/**
* getTagStart
*
* Create the HTML for the start of a tag
*
* @param type - The type of tag
* @param ... - <object> || <name, value>
*/
function getTagStart(type) {
var attributes = new Attributes(arguments, 1);
// Do NOT test for null... we only care if it EXISTS
if (isDefined(attributes.href)) {
// Remove href for tags that don't use it, since
// we'll probably be anchoring the tag elsewhere
if (isAnchorableTag(type)) {
delete attributes.href;
}
}
var buffer = new StringBuffer();
buffer.append("<", type.toLowerCase(), " ", attributes.toString(), ">");
return buffer.toString();
}
/**
* getTagEnd
*
* Create the HTML for the end of a tag
*
* @param type - The type of tag
* @param ... - <object> || <name, value>
*/
function getTagEnd(type) {
var buffer = new StringBuffer();
buffer.append("</", type.toLowerCase(), ">");
return buffer.toString();
}
/**
* getTag
*
* Create the HTML for an entire tag
*
* If a tag that doesn't normally use href as an attribute has it set,
* the output will be conveniently wrapped in an anchor tag for you!!!
*
* @param type - The type of tag
* @param content - The content
* @param ... - <object> || <name, value>
*/
function getTag(type, content) {
var attributes = new Attributes(arguments, 2);
return getTagStart(type, attributes) + content + getTagEnd(type);
}
/**
* isAnchorableTag
*
* Tags that have an href attribute will be wrapped in an anchor by the
* wrapTag function as long as they are not found in TAGS_THAT_USE_HREF
*
* getTagStart also uses this value, to remove hrefs from any tags that
* don't normally use them. The assumption is that it's there for wrap!
*
* @param type - The type of tag
*/
function isAnchorableTag(type) {
return !isDefined(TAGS_THAT_USE_HREF[type]);
}
/**
* wrapTag
*
* Automatically parse a tag and it's attributes looking for anything
* out of the ordinary, for example, an href attribute on an img tag.
*
* When it finds certain combinations it will perform shortcuts, like
* wrapping tags that don't normally use an href with an anchor. Woo!
*
* @param tag - The html representing the tag
* @param attributes - The tag's attributes
*/
function wrapTag(tag, attributes) {
// Do NOT test for null... we only care if it EXISTS
if (isDefined(attributes.href)) {
var type = tag.substr(1);
type = type.split(" ")[0];
type = type.split(">")[0];
// Sanity check to prevent infinite recursion on
// mistakenly wrapped tags that really use href!
if (isAnchorableTag(type)) {
return getA(attributes.href, tag);
}
}
return tag;
}
//-----------------------------------------------------------------------------
// Specific HTML Tag Rendering
//-----------------------------------------------------------------------------
/**
* getA
*
* Create the HTML for an anchor tag
*
* @param href - The url to visit, only renders the start of the tag if null
* @param content - The content
* @param ... - <object> || <name, value>
*/
function getA(href, content) {
var attributes = new Attributes(arguments, 2);
if (href != null) {
attributes.href = href;
}
// Only render the start of the anchor if there's no href
// Dont wrap the tag, it doesn't make sense!
if (content != null) {
return getTag("a", content, attributes);
} else {
return getTagStart("a", attributes);
}
}
/**
* writeA
*
* Write the HTML for an anchor tag
*
* @param href - The url to visit, only renders the start of the tag if null
* @param content - The content
* @param ... - <object> || <name, value>
*/
function writeA(href, content) {
var attributes = new Attributes(arguments, 2);
write(getA(href, content, attributes));
}
/**
* getDiv
*
* Create the HTML for a div tag
*
* @param clazz - The class, 'clazz' due to JavaScript namespace collision
* @param content - The content
* @param ... - <object> || <name, value>
*/
function getDiv(clazz, content) {
var attributes = new Attributes(arguments, 2);
// Always CONCATENTATE styles, they're cumulative
if (clazz != null) {
attributes.setAttribute("clazz", clazz);
}
// Robustness... null should not render "null"!!!
if (content == null) {
content = "";
}
return wrapTag(getTag("div", content, attributes), attributes);
}
/**
* writeDiv
*
* Write the HTML for a div tag
*
* @param clazz - The class, 'clazz' due to JavaScript namespace collision
* @param content - The content
* @param ... - <object> || <name, value>
*/
function writeDiv(clazz, content) {
var attributes = new Attributes(arguments, 2);
write(getDiv(clazz, content, attributes));
}
/**
* getImg
*
* Create the HTML for an img tag
*
* @param src - The url of the image file
* @param title - The title of the image
* @param ... - <object> || <name, value>
*/
function getImg(src, title) {
var attributes = new Attributes(arguments, 2);
if (src != null) {
attributes.src = src;
}
if (title != null) {
attributes.title = title;
}
return wrapTag(getTagStart("img", attributes), attributes);
}
/**
* writeImg
*
* Write the HTML for an img tag
*
* @param src - The url of the image file
* @param title - The title of the image
* @param ... - <object> || <name, value>
*/
function writeImg(src, title) {
var attributes = new Attributes(arguments, 2);
write(getImg(src, title, attributes));
}
/**
* getLI
*
* Create the HTML for an list item tag
*
* @param content - The content
* @param ... - <object> || <name, value>
*/
function getLI(content) {
var attributes = new Attributes(arguments, 1);
// Dont wrap the tag, it doesn't make sense!
return getTag("li", content, attributes);
}
/**
* writeLI
*
* Write the HTML for an list item tag
*
* @param content - The content
* @param ... - <object> || <name, value>
*/
function writeLI(content) {
var attributes = new Attributes(arguments, 1);
write(getLI(content, attributes));
}
/**
* getOL
*
* Create the HTML for an ordered list tag
*
* @param content - The content
* @param ... - <object> || <name, value>
*/
function getOL(content) {
var attributes = new Attributes(arguments, 1);
return wrapTag(getTag("ol", content, attributes), attributes);
}
/**
* writeOL
*
* Write the HTML for an ordered list tag
*
* @param content - The content
* @param ... - <object> || <name, value>
*/
function writeOL(content) {
var attributes = new Attributes(arguments, 1);
write(getOL(content, attributes));
}
/**
* getSpan
*
* Create the HTML for a span tag
*
* @param clazz - The class, 'clazz' due to JavaScript namespace collision
* @param content - The content
* @param ... - <object> || <name, value>
*/
function getSpan(clazz, content) {
var attributes = new Attributes(arguments, 2);
// Always CONCATENTATE styles, they're cumulative
if (clazz != null) {
attributes.setAttribute("clazz", clazz);
}
// Robustness... null should not render "null"!!!
if (content == null) {
content = "";
}
return wrapTag(getTag("span", content, attributes), attributes);
}
/**
* writeSpan
*
* Write the HTML for a span tag
*
* @param clazz - The class, 'clazz' due to JavaScript namespace collision
* @param content - The content
* @param ... - <object> || <name, value>
*/
function writeSpan(clazz, content) {
var attributes = new Attributes(arguments, 2);
write(getSpan(clazz, content, attributes));
}
/**
* getTitle
*
* Create the HTML for a title tag
*
* @param title - The title
* @param ... - <object> || <name, value>
*/
function getTitle(title) {
var attributes = new Attributes(arguments, 1);
return wrapTag(getTag("title", title, attributes), attributes);
}
/**
* writeTitle
*
* Write the HTML for a title tag
*
* @param title - The title
* @param ... - <object> || <name, value>
*/
function writeTitle(title) {
var attributes = new Attributes(arguments, 1);
write(getTitle(title, attributes));
}
/**
* getUL
*
* Create the HTML for an unordered list tag
*
* @param content - The content
* @param ... - <object> || <name, value>
*/
function getUL(content) {
var attributes = new Attributes(arguments, 1);
return wrapTag(getTag("ul", content, attributes), attributes);
}
/**
* writeUL
*
* Write the HTML for an unordered list tag
*
* @param content - The content
* @param ... - <object> || <name, value>
*/
function writeUL(content) {
var attributes = new Attributes(arguments, 1);
write(getUL(content, attributes));
}
//-----------------------------------------------------------------------------
// Custom HTML Rendering For Head Tag Elements
//-----------------------------------------------------------------------------
/**
* getLink
*
* Create the HTML for an link tag
*
* @param href - The url of the link
* @param ... - <object> || <name, value>
*/
function getLink(href) {
var attributes = new Attributes(arguments, 1);
if (href != null) {
attributes.href = href;
}
// Dont wrap the tag, it doesn't make sense!
return getTagStart("link", attributes);
}
/**
* writeLink
*
* Write the HTML for an link tag
*
* @param href - The url of the link
* @param ... - <object> || <name, value>
*/
function writeLink(href) {
var attributes = new Attributes(arguments, 1);
write(getLink(href, attributes));
}
/**
* getMetaHttpEquiv
*
* Create the HTML for a meta http-equiv as used in the head tag
*
* @param httpEquiv - The value of the http-equiv attribute
* @param content - The content ATTRIBUTE
* @param ... - <object> || <name, value>
*/
function getMetaHttpEquiv(httpEquiv, content) {
var attributes = new Attributes(arguments, 2);
if (httpEquiv != null) {
attributes.httpEquiv = httpEquiv;
}
if (content != null) {
attributes.content = content;
}
// Dont wrap the tag, it doesn't make sense!
return getTagStart("meta", attributes);
}
/**
* writetMetaHttpEquiv
*
* Write the HTML for a meta http-equiv as used in the head tag
*
* @param httpEquiv - The value of the http-equiv attribute
* @param content - The content ATTRIBUTE
* @param ... - <object> || <name, value>
*/
function writetMetaHttpEquiv(httpEquiv, content) {
var attributes = new Attributes(arguments, 2);
write(getMetaHttpEquiv(httpEquiv, content, attributes));
}
/**
* getMetaName
*
* Create the HTML for a meta http-equiv as used in the head tag
*
* @param name - The value of the name attribute
* @param content - The content ATTRIBUTE
* @param ... - <object> || <name, value>
*/
function getMetaName(name, content) {
var attributes = new Attributes(arguments, 2);
if (name != null) {
attributes.name = name;
}
if (content != null) {
attributes.content = content;
}
// Dont wrap the tag, it doesn't make sense!
return getTagStart("meta", attributes);
}
/**
* writeMetaName
*
* Write the HTML for a meta http-equiv as used in the head tag
*
* @param name - The value of the name attribute
* @param content - The content ATTRIBUTE
* @param ... - <object> || <name, value>
*/
function writeMetaName(name, content) {
var attributes = new Attributes(arguments, 2);
write(getMetaName(name, content, attributes));
}
/**
* getStyleSheet
*
* Create the HTML for a style sheet as used in the head tag
*
* @param href - The url of the style sheet
* @param ... - <object> || <name, value>
*/
function getStyleSheet(href) {
var attributes = new Attributes(arguments, 1);
if (href != null) {
attributes.href = href;
}
attributes.rel = "stylesheet";
attributes.type = "text/css";
// Dont wrap the tag, it doesn't make sense!
return getTagStart("link", attributes);
}
/**
* writeStyleSheet
*
* Write the HTML for a style sheet as used in the head tag
*
* @param href - The url of the style sheet
* @param ... - <object> || <name, value>
*/
function writeStyleSheet(href) {
var attributes = new Attributes(arguments, 1);
write(getStyleSheet(href, attributes));
}
//#############################################################################
// List - A Convenience Class To Define/Render AN HTML List (Down With Tables!)
//#############################################################################
/**
* List 'Constructor'
*
* @param contentOpt - Optional content
* @param orderedOpt - Is ordered list, defaults to false
* @param listClazzOpt - Optional style for entire list
* @param itemClazzOpt - Optional style for list items
* @param lastItemClazzOpt - Optional style for last list item
* @param ... - <object> || <name, value>
*/
function List(contentOpt, isOrderedOpt, listClazzOpt, itemClazzOpt, firstItemClazzOpt, lastItemClazzOpt) {
// class variables --------------------------------------------------------
this.attributes = new Attributes(arguments, 6);
this.items = new Array();
this.itemAttributes = new Array();
// construct class --------------------------------------------------------
// Default to false
if (isOrderedOpt != null) {
this.isOrdered = isOrderedOpt;
} else {
this.isOrdered = false;
}
// Add list's class right to attributes!
if (listClazzOpt != null) {
this.attributes.setAttribute("clazz", listClazzOpt);
}
if (itemClazzOpt != null) {
this.itemClazz = itemClazzOpt;
}
if (firstItemClazzOpt != null) {
this.firstItemClazz = firstItemClazzOpt;
}
if (lastItemClazzOpt != null) {
this.lastItemClazz = lastItemClazzOpt;
}
// Then add the optional content
if (contentOpt != null) {
this.add(contentOpt);
}
}
/**
* List.add
*
* Add items to the list, which may be:
*
* - Any Object
* - Nested arrays containing a mix of either of the above
*
* @param content - The content
* @param ... - <object> || <name, value>
*/
List.prototype.add = function(content) {
var attributes = new Attributes(arguments, 1);
if (content != null) {
if (isArray(content)) {
for (var ii = 0; ii < content.length; ii++) {
this.add(content[ii]);
}
} else {
this.items[this.items.length] = content.toString();
this.itemAttributes[this.itemAttributes.length] = attributes;
}
}
}
/**
* List.toString
*
* Gets the HTML used to render an List object
*/
List.prototype.toString = function() {
var lastIndex = this.items.length - 1;
// Loop through the items, embedding them in li tags
var buffer = new StringBuffer("\n", 1, true);
for (var ii = 0; ii < this.items.length; ii++) {
var item = "\n".append(this.items[ii].toString().prependLines(), "\n");
var clazz = this.itemClazz;
// The first/last items might use custom classes
if (this.firstItemClazz && ii == 0) {
clazz = this.firstItemClazz;
} else if (this.lastItemClazz && ii == lastIndex) {
clazz = this.lastItemClazz;
}
buffer.append(getLI(item, this.itemAttributes[ii], "clazz", clazz));
}
buffer.newline();
// Then embed this data into the right type of list!
return this.isOrdered ?
getOL(buffer.toString(), this.attributes) :
getUL(buffer.toString(), this.attributes);
}