// Designer namespace
var Designer = {

	// Options namespace
	//     Contains Designer options
	Options : {

		// Indicates whether or not pricing is handled by the server
		DynamicPricing : false,

		// Indicates whether the Eagle logo checkbox is allowed
		AllowLogo : false
	},

	// Constants namespace
	//     Defines useful constants that are used throughout the script
	Constants : {

		// When no label colour is defined for a given type, this ID inside styles->label is used
		FallbackStylesLabelId : 1,

		// Print styles are only available on the label style defined below
		StylesPrintAvailableOnStylesLabelId : 1,

		// When a label colour other than the defined above is selected, only the given print colour will be available
		StylesPrintFallbackId : 1,

		// Holds the last parameters used to render the preview image
		LastPreviewParams : null,

		// True if the preview is updated on every change, false otherwise
		LivePreview : true,

		// Millimetre to pixels factor
		Mm2PxFactor : 3.77952756
	},

	// Data namespace
	//     Contains the state of the designer
	Data : {

		// Holds the index of the active step
		ActiveStep : false,

		// Holds the name of the DOM element that contains a copy of the parameters used to build the label
		ParamsContainerName : null,

		// The DOM element that contains a copy of the parameters used to build the label
		ParamsContainerObject : null,

		// The DOM element that hosts the designer
		HostObject : null,

		// The DOM element that hosts the preview image
		PreviewImageObject : null,

		// The DOM element that hosts the text input panel
		TextPanelObject : null,

		// The DOM element that hosts the price panel
		PricePanelObject : null,

		// The DOM element that hosts the tooltip
		TooltipObject : null,

		// The DOM element of the selected shape or NULL
		SelectedShape : null,

		// The DOM element of the selected type or NULL
		SelectedType : null,

		// The DOM element of the selected size or NULL
		SelectedSize : null,

		// The DOM element of the selected label colour or NULL
		SelectedStylesLabel : null,

		// The DOM element of the selected print colour or NULL
		SelectedStylesPrint : null,

		// Holds the data entered in the text input panel
		TextPanelData : [],

		// Holds the selected font
		SelectedFont : null,

		// Holds the selected line spacing
		SelectedLineSpacing : null,

		// Holds the 'Insert FLP Eagle logo' option state
		SelectedLogo : false,

		// Holds the selected horizontal and vertical align
		SelectedAlign : 'MC',

		// Indicates whether or not static options are visible
		StaticOptionsVisible : false,

		// Holds a table with all AJAX requests
		AjaxPriceRequests : [],

		// True to suspend updates to the preview, false otherwise
		SuspendUpdate : false,

		// True when the state of the designer was restored, false otherwise
		Restored : false
	},

	// Blocks namespace
	//     Handles HTML building
	Blocks : {

		// Events namespace
		//     Provides callbacks to filter the data used to build the blocks
		Events : {

			OnGetShapeItem : function() { return true; },
			OnGetTypeItem : function() { return true; },
			OnGetSizeItem : function() { return true; },
			OnGetStylesLabelItem : function() { return true; },
			OnGetStylesPrintItem : function() { return true; },
			OnGetTextPanel : function() {},

			OnGetSizeWrapItemName : function(type, sizeName) { return sizeName; }
		},

		// Builds the HTML code for the 'Shape' block
		GetShape : function() {

			Designer.SetActiveStep(1);

			var html = '';

			// Output header
			html += '<table class="shape" cellspacing="0" cellpadding="0">';
			html +=   '<tbody>';

			var type;
			var shapeName;

			// Loop all types and extract the shape
			for (var key in Designer.Model.types) {

				// Get a reference to the current type
				type = Designer.Model.types[key];

				// Make sure to invoke the callback
				if ( ! Designer.Blocks.Events.OnGetShapeItem(type)) {

					continue;
				}

				// Check if the current shape is already in the list
				if (html.indexOf('/shapes/' + type.shape + '.') > 0) {

					continue;
				}

				// Build shape name
				shapeName = (type.shape.charAt(0).toUpperCase() + type.shape.substr(1));

				// Output type selection link
				html +=     '<tr>';
				html +=       '<td class="text"><a href="/shape/' + type.shape + '" onclick="Designer.Actions.SelectShape(this); return false;" onmouseover="Designer.Tooltip.Show(this.getElementsByTagName(\'img\')[0]);" onmouseout="Designer.Tooltip.Hide();"><img src="' + Designer.Base + 'res/images/shapes/' + type.shape + '.png" width="32" height="32" alt="' + shapeName + ' Shape" title="" /></a></td>';
				html +=       '<td class="selection"><img src="' + Designer.Base + 'res/images/blank.gif" width="12" height="32" alt="" title="" /></td>';
				html +=     '</tr>';
			}

			// Output footer
			html +=   '</tbody>';
			html += '</table>';

			return html;
		},

		// Builds the HTML code for the 'Type' block based on the selected shape
		GetType : function() {

			Designer.SetActiveStep(2);

			// Get the selected shape (last part of the URL)
			var selectedShape = Designer.Data.SelectedShape.href.split('/');
			selectedShape = selectedShape[selectedShape.length - 1];

			var html = '';

			// Output header
			html += '<table class="type" cellspacing="0" cellpadding="0">';
			html +=   '<tbody>';

			var type;
			var typeName;
			var typeNameShort;

			// Loop all types and match shape
			for (var key in Designer.Model.types) {

				// Get a reference to the current type
				type = Designer.Model.types[key];

				// Match shape
				if (type.shape != selectedShape) {

					continue;
				}

				// Build type name
				if (type.type == 'badges') {

					typeName = 'Badges';
					typeNameShort = 'BG';

				} else if (type.type == 'cardsback') {

					typeName = 'Back of Cards';
					typeNameShort = 'CB';

				} else if (type.type == 'cards') {

					typeName = 'Business Cards';
					typeNameShort = 'BC';

				} else if (type.type == 'letterheads') {

					typeName = 'Letterheads';
					typeNameShort = 'LH';

				} else if (type.type == 'complimentslips') {

					typeName = 'Compliment Slips';
					typeNameShort = 'CS';

				} else if (type.styles.full_colours) {

					typeName = 'Full Colour';
					typeNameShort = 'FC';

				} else if (type.styles.print.length == 3) {

					typeName = 'Rubber Stamps';
					typeNameShort = 'RS';

				} else {

					typeName = 'Single Colour';
					typeNameShort = 'SC';
				}

				// Make sure to invoke the callback
				if ( ! Designer.Blocks.Events.OnGetTypeItem(type, typeNameShort, typeName)) {

					continue;
				}

				// Check if the current type is already in the list
				if (html.indexOf('>' + typeName + '<') > 0) {

					continue;
				}

				// Output type selection link
				html +=     '<tr>';
				html +=       '<td class="text"><a href="/type/' + typeNameShort + '" onclick="Designer.Actions.SelectType(this); return false;">' + typeName + '</a></td>';
				html +=       '<td class="selection"><img src="' + Designer.Base + 'res/images/blank.gif" width="12" height="24" alt="" title="" /></td>';
				html +=     '</tr>';
			}

			// Output footer
			html +=   '</tbody>';
			html += '</table>';

			var container = Designer.Data.HostObject.childNodes[0].getElementsByTagName('tbody')[0].childNodes[0].childNodes;

			// Replace 'Type' with the new HTML
			container[1].innerHTML = html;
			container[1].className = container[1].className.replace('empty', '');

			// Update instructions
			Designer.Blocks.SetColumnNames(1);

			// Erase progress
			container[2].innerHTML = '&nbsp;';
			container[3].innerHTML = '&nbsp;';
			container[4].innerHTML = '&nbsp;';

			container[2].className += (container[2].className.indexOf('empty') > 0 ? '' : ' empty');
			container[3].className += (container[2].className.indexOf('empty') > 0 ? '' : ' empty');
			container[4].className += (container[2].className.indexOf('empty') > 0 ? '' : ' empty');
		},

		// Builds the HTML code for the 'Size' block based on the selected shape and type
		GetSize : function() {

			Designer.SetActiveStep(3);

			// Get the selected shape (last part of the URL)
			var selectedShape = Designer.Data.SelectedShape.href.split('/');
			selectedShape = selectedShape[selectedShape.length - 1];

			// Get the selected type (last part of the URL)
			var selectedType = Designer.Data.SelectedType.href.split('/');
			selectedType = selectedType[selectedType.length - 1];

			var html = '';

			// Output header
			html += '<table class="size" cellspacing="0" cellpadding="0">';
			html +=   '<tbody>';

			var type;
			var typeNameShort;
			var sizeName;

			// Loop all types and match shape and type
			for (var key in Designer.Model.types) {

				// Get a reference to the current type
				type = Designer.Model.types[key];

				// Build type name
				if (type.type == 'badges') {

					typeNameShort = 'BG';

				} else if (type.type == 'cardsback') {

					typeNameShort = 'CB';

				} else if (type.type == 'cards') {

					typeNameShort = 'BC';

				} else if (type.type == 'letterheads') {

					typeNameShort = 'LH';

				} else if (type.type == 'complimentslips') {

					typeNameShort = 'CS';

				} else if (type.styles.full_colours) {

					typeNameShort = 'FC';

				} else if (type.styles.print.length == 3) {

					typeNameShort = 'RS';

				} else {

					typeNameShort = 'SC';
				}

				// Match shape and type
				if ((type.shape != selectedShape) ||
					(typeNameShort != selectedType)) {

					continue;
				}

				if (type.size.height > 0) {

					sizeName = type.size.width + '<small>mm</small>&nbsp;x&nbsp;' + type.size.height + '<small>mm</small>';

				} else {

					sizeName = type.size.width + '<small>mm diameter</small>';
				}

				// Make sure to invoke the callback
				if ( ! Designer.Blocks.Events.OnGetSizeItem(type)) {

					continue;
				}

				sizeName = Designer.Blocks.Events.OnGetSizeWrapItemName(type, sizeName);

				// Check if the current size is already in the list
				if (html.indexOf('>' + sizeName + '<') > 0) {

					continue;
				}

				// Output type selection link
				html +=     '<tr>';
				html +=       '<td class="text"><a href="/id/' + type.id + '" onclick="Designer.Actions.SelectSize(this); return false;" onmouseover="Designer.Tooltip.ShowPriceGuide(this);" onmouseout="Designer.Tooltip.Hide();">' + sizeName + '</a></td>';
				html +=       '<td class="selection"><img src="' + Designer.Base + 'res/images/blank.gif" width="12" height="24" alt="" title="" /></td>';
				html +=     '</tr>';
			}

			// Output footer
			html +=   '</tbody>';
			html += '</table>';

			var container = Designer.Data.HostObject.childNodes[0].getElementsByTagName('tbody')[0].childNodes[0].childNodes;

			// Replace 'Size' with the new HTML
			container[2].innerHTML = html;
			container[2].className = container[1].className.replace('empty', '');

			Designer.Data.SelectedSize = null;

			// Update instructions
			Designer.Blocks.SetColumnNames(2);

			// Erase progress
			container[3].innerHTML = '&nbsp;';
			container[4].innerHTML = '&nbsp;';

			container[3].className += (container[2].className.indexOf('empty') > 0 ? '' : ' empty');
			container[4].className += (container[2].className.indexOf('empty') > 0 ? '' : ' empty');
		},

		// Builds the HTML code for the 'Label Colour' block based on the selected shape and type
		GetStylesLabel : function() {

			Designer.SetActiveStep(4);

			// Get the selected type ID (last part of the URL)
			var selectedTypeId = Designer.Data.SelectedSize.href.split('/');
			selectedTypeId = parseInt(selectedTypeId[selectedTypeId.length - 1]);

			var lengthOfStyles = 0;

			var html = '';

			// Output header
			html += '<table class="styles-label" cellspacing="0" cellpadding="0">';
			html +=   '<tbody>';

			// Get the selected type by ID and make sure we have a valid object
			var type = Designer.Model.types[selectedTypeId];

			var labelSafeName;

			if (typeof (type) == 'object') {

				var labelStyle;

				// Check if we have at least one entry, if not, add White as default
				if (type.styles.label.length < 1) {

					type.styles.label[0] = Designer.Constants.FallbackStylesLabelId;
				}

				// Loop all defined label styles
				for (var i = 0; i < type.styles.label.length; i ++) {

					// Get a reference to the current label style
					labelStyle = Designer.Model.styles.label[type.styles.label[i]];

					// Make sure the label style exists
					if (typeof (labelStyle) != 'object') {

						continue;
					}

					// Build the 'safe' name of the current label style
					labelSafeName = labelStyle.name.toLowerCase().replace(/[^A-Z0-9]/ig, '-');

					// Make sure to invoke the callback
					if ( ! Designer.Blocks.Events.OnGetStylesLabelItem(type, labelStyle, labelSafeName)) {

						continue;
					}

					// Output label style selection link
					html +=     '<tr>';
					html +=       '<td class="text"><a href="/styles/label/' + labelStyle.id + '" onclick="Designer.Actions.SelectLabelStyle(this); return false;" onmouseover="Designer.Tooltip.Show(this.getElementsByTagName(\'img\')[0]);" onmouseout="Designer.Tooltip.Hide();"><img src="' + Designer.Base + 'res/images/styles/label/' + labelSafeName + '.png" width="24" height="24" alt="' + (labelStyle.name) + '" title="" /></a></td>';
					html +=       '<td class="selection"><img src="' + Designer.Base + 'res/images/blank.gif" width="12" height="24" alt="" title="" /></td>';
					html +=     '</tr>';

					lengthOfStyles ++;
				}
			}

			// Output footer
			html +=   '</tbody>';
			html += '</table>';

			var container = Designer.Data.HostObject.childNodes[0].getElementsByTagName('tbody')[0].childNodes[0].childNodes;

			// Replace 'Label Colour' with the new HTML
			container[3].innerHTML = html;
			container[3].className = container[1].className.replace('empty', '');

			// Update instructions
			Designer.Blocks.SetColumnNames(3);

			// Erase progress
			container[4].innerHTML = '&nbsp;';

			container[4].className += (container[2].className.indexOf('empty') > 0 ? '' : ' empty');

			// Check if we have only one style available
			if (lengthOfStyles == 1) {

				Designer.Actions.SelectLabelStyle(container[3].getElementsByTagName('a')[0]);
			}
		},

		// Builds the HTML code for the 'Print Colour' block based on the selected label style
		GetStylesPrint : function() {

			Designer.SetActiveStep(5);

			// Get the selected type ID (last part of the URL)
			var selectedTypeId = Designer.Data.SelectedSize.href.split('/');
			selectedTypeId = parseInt(selectedTypeId[selectedTypeId.length - 1]);

			// Get the selected label style ID (last part of the URL)
			var selectedStylesLabelId = Designer.Data.SelectedStylesLabel.href.split('/');
			selectedStylesLabelId = parseInt(selectedStylesLabelId[selectedStylesLabelId.length - 1]);

			var lengthOfStyles = 0;

			var html = '';

			// Output header
			html += '<table class="styles-print" cellspacing="0" cellpadding="0">';
			html +=   '<tbody>';

			// Get the selected type and label style by ID and make sure we have a valid object
			var type = Designer.Model.types[selectedTypeId];
			var labelStyle = Designer.Model.styles.label[selectedStylesLabelId];

			var printStyle;
			var printSafeName;

			if ((typeof (type) == 'object') &&
				(typeof (labelStyle) == 'object')) {

				// Check if this is a 'Full Colour' type
				if (type.styles.full_colours) {

					// Output print style selection link
					html +=     '<tr>';
					html +=       '<td class="text"><a href="/styles/print/FC" onclick="Designer.Actions.SelectPrintStyle(this); return false;">Full Colour</a></td>';
					html +=       '<td class="selection"><img src="' + Designer.Base + 'res/images/blank.gif" width="12" height="24" alt="" title="" /></td>';
					html +=     '</tr>';

					lengthOfStyles ++;

				  // Check if the selected label style is allowed to have print styles
				} else if (labelStyle.id == Designer.Constants.StylesPrintAvailableOnStylesLabelId) {

					// Loop all defined print styles
					for (var i = 0; i < type.styles.print.length; i ++) {

						// Get a reference to the current print style
						printStyle = Designer.Model.styles.print[type.styles.print[i]];

						// Make sure the print style exists
						if (typeof (printStyle) != 'object') {

							continue;
						}

						// Build the 'safe' name of the current print style
						printSafeName = printStyle.name.toLowerCase().replace(/[^A-Z0-9]/ig, '-');

						// Make sure to invoke the callback
						if ( ! Designer.Blocks.Events.OnGetStylesPrintItem(type, printStyle, printSafeName)) {

							continue;
						}

						// Output print style selection link
						html +=     '<tr>';
						html +=       '<td class="text"><a href="/styles/print/' + printStyle.id + '" onclick="Designer.Actions.SelectPrintStyle(this); return false;" onmouseover="Designer.Tooltip.Show(this.getElementsByTagName(\'img\')[0]);" onmouseout="Designer.Tooltip.Hide();"><img src="' + Designer.Base + 'res/images/styles/print/' + printSafeName + '.png" width="24" height="24" alt="' + (printStyle.name) + '" title="" /></a></td>';
						html +=       '<td class="selection"><img src="' + Designer.Base + 'res/images/blank.gif" width="12" height="24" alt="" title="" /></td>';
						html +=     '</tr>';

						lengthOfStyles ++;
					}

				  // Print styles for the selected label style are not allowed, fallback to the default print style
				} else {

					// Get the fallback print style
					printStyle = Designer.Model.styles.print[Designer.Constants.StylesPrintFallbackId];

					// Build the 'safe' name of the current print style
					printSafeName = printStyle.name.toLowerCase().replace(/[^A-Z0-9]/ig, '-');

					// Output print style selection link
					html +=     '<tr>';
					html +=       '<td class="text"><a href="/styles/print/' + printStyle.id + '" onclick="Designer.Actions.SelectPrintStyle(this); return false;" onmouseover="Designer.Tooltip.Show(this.getElementsByTagName(\'img\')[0]);" onmouseout="Designer.Tooltip.Hide();"><img src="' + Designer.Base + 'res/images/styles/print/' + printSafeName + '.png" width="24" height="24" alt="' + (printStyle.name) + '" title="" /></a></td>';
					html +=       '<td class="selection"><img src="' + Designer.Base + 'res/images/blank.gif" width="12" height="24" alt="" title="" /></td>';
					html +=     '</tr>';

					lengthOfStyles ++;
				}
			}

			// Output footer
			html +=   '</tbody>';
			html += '</table>';

			var container = Designer.Data.HostObject.childNodes[0].getElementsByTagName('tbody')[0].childNodes[0].childNodes;

			// Replace 'Print Colour' with the new HTML
			container[4].innerHTML = html;
			container[4].className = container[1].className.replace('empty', '');

			// Update instructions
			Designer.Blocks.SetColumnNames(4);

			// Check if we have only one style available
			if (lengthOfStyles == 1) {

				Designer.Actions.SelectPrintStyle(container[4].getElementsByTagName('a')[0]);
			}
		},

		// Builds the HTML code for the text input panel
		GetTextPanel : function() {

			Designer.SetActiveStep(6);

			// Get the selected type ID (last part of the URL)
			var selectedTypeId = Designer.Data.SelectedSize.href.split('/');
			selectedTypeId = parseInt(selectedTypeId[selectedTypeId.length - 1]);

			// Get the selected type by ID
			var type = Designer.Model.types[selectedTypeId];
			var printStyle;

			var key;
			var html = '';

			// Output header
			html += '<table class="text-panel' + (type.styles.full_colours ? ' text-full-colours' : '') + '" cellspacing="0" cellpadding="0">';
			html +=   '<caption>Use the boxes below to enter the text you want to see on your label and choose how each line should look.</caption>';
			html +=   '<thead>';
			html +=     '<tr>';
			html +=       '<th class="id">&nbsp;</th>';
			html +=       '<th class="line-text">Line Text</th>';
			html +=       '<th class="style">Style</th>';
			html +=       '<th class="size">Size</th>';
			html +=       (type.styles.full_colours ? '<th class="colour">Colour</th>' : '');
			html +=     '</tr>';
			html +=   '</thead>';
			html +=   '<tbody>';

			var typedText;
			var selectedStyle;
			var selectedSize;
			var selectedColour;

			var j;

			// Loop all text rows and build input elements
			for (var i = 0; i < type.text.rows; i ++) {

				// Check if the user has already entered data for this row
				if (typeof (Designer.Data.TextPanelData[i]) == 'undefined') {

					Designer.Data.TextPanelData[i] = {};

					// Default values are used
					typedText = Designer.Data.TextPanelData[i].text = '';
					selectedStyle = Designer.Data.TextPanelData[i].style = 'normal';
					selectedSize = Designer.Data.TextPanelData[i].size = '12';
					selectedColour = Designer.Data.TextPanelData[i].colour = '000000';
					textFixed = Designer.Data.TextPanelData[i].fixed = false;

				} else {

					// Data already entered is restored
					typedText = Designer.Data.TextPanelData[i].text.substr(0, type.text.columns);
					selectedStyle = Designer.Data.TextPanelData[i].style;
					selectedSize = Designer.Data.TextPanelData[i].size;
					selectedColour = Designer.Data.TextPanelData[i].colour;
					textFixed = Designer.Data.TextPanelData[i].fixed;
				}

				html +=     '<tr>';
				html +=       '<td class="id">' + (i + 1) + '.</td>';

				html +=       '<td' + '' /*@cc_on + (type.styles.full_colours ? ' class="with-colours"' : '') @*/ + '><input type="text" name="stickylabel_designer[preview][text][' + i + ']" id="stickylabel_designer_preview_text_' + i + '" maxlength="' + type.text.columns + '" value="' + htmlescape(typedText) + '" onchange="Designer.Actions.UpdateTextLine(this);" onkeydown="Designer.Actions.UpdateTextLine(this);" onkeyup="Designer.Actions.UpdateTextLine(this);" onkeypress="Designer.Actions.UpdateTextLine(this);"' + (type.styles.full_colours ? ' style="color: #' + selectedColour + ';"' : '') + (textFixed ? ' readonly="readonly" class="fixed"' : '') + ' /></td>';

				html +=       '<td><select name="stickylabel_designer[preview][style][' + i + ']" id="stickylabel_designer_preview_style_' + i + '" onchange="Designer.Data.TextPanelData[' + i + '].style = this.options[this.selectedIndex].value; Designer.LivePreview(this);">';
				html +=         '<option value="normal"' + (selectedStyle == 'normal' ? ' selected="selected"' : '') + '>Normal</option>';
				html +=         '<option value="bold"' + (selectedStyle == 'bold' ? ' selected="selected"' : '') + '>Bold</option>';
				html +=         '<option value="italic"' + (selectedStyle == 'italic' ? ' selected="selected"' : '') + '>Italic</option>';
				html +=         '<option value="bolditalic"' + (selectedStyle == 'bolditalic' ? ' selected="selected"' : '') + '>Bold-Italic</option>';
				html +=       '</select></td>';

				var pointSizes = [8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72];

				html +=       '<td><select name="stickylabel_designer[preview][size][' + i + ']" id="stickylabel_designer_preview_size_' + i + '" onchange="Designer.Data.TextPanelData[' + i + '].size = this.options[this.selectedIndex].value; Designer.LivePreview(this);">';

				for (j = 0; j < pointSizes.length; j ++) {

					html += '<option value="' + pointSizes[j] + '"' + (selectedSize == pointSizes[j] ? ' selected="selected"' : '') + '>' + pointSizes[j] + ' pt</option>';
				}

				html +=       '</select></td>';

				// If this is a full-colour type, output print colour selector
				if (type.styles.full_colours) {

					// Output selector header
					html +=       '<td><select name="stickylabel_designer[preview][colour][' + i + ']" id="stickylabel_designer_preview_colour_' + i + '" '
					              + 'onchange="Designer.Data.TextPanelData[' + i + '].colour = this.options[this.selectedIndex].value; '
					              + '$(\'stickylabel_designer_preview_text_' + i + '\').style.color = \'#\' + this.options[this.selectedIndex].value; '
					              + 'Designer.LivePreview(this);">';

					// Loop all available print types
					for (key in Designer.Model.styles.print) {

						// Get a reference to the current print style
						printStyle = Designer.Model.styles.print[key];

						html +=         '<option value="' + printStyle.hex_value + '"' + (selectedColour == printStyle.hex_value ? ' selected="selected"' : '') + '>' + printStyle.name + '</option>';
					}

					// Output selector footer
					html +=       '</select></td>';
				}

				html +=     '</tr>';
			}

			// Output font and line spacing selectors
			var selectedFont;
			var selectedLineSpacing;

			html +=     '<tr>';
			html +=       '<th colspan="' + (type.styles.full_colours ? 3 : 2) + '" scope="row">Font Style</th>';
			html +=       '<th colspan="2" scope="row">Adjust line spacing</th>';
			html +=     '</tr>';
			html +=     '<tr>';
			html +=       '<td>&nbsp;</td>';

			// Check if the user has already selected a font
			if (Designer.Data.SelectedFont == null) {

				selectedFont = Designer.Data.SelectedFont = 'verdana.ttf';

			} else {

				selectedFont = Designer.Data.SelectedFont;
			}

			html +=       '<td' + (type.styles.full_colours ? ' colspan="2"' : '') + ' scope="row">';
			html +=         '<select name="stickylabel_designer[preview][font]" id="stickylabel_designer_preview_font" onchange="Designer.Data.SelectedFont = this.options[this.selectedIndex].value; Designer.LivePreview(this);">';

			var font;

			// Loop all fonts defined in the model and output an option element
			for (key in Designer.Model.fonts) {

				font = Designer.Model.fonts[key];

				html +=           '<option value="' + font.filename + '"' + (selectedFont == font.filename ? ' selected="selected"' : '') + '>' + font.name + '</option>';
			}

			html +=         '</select>';
			html +=       '</td>';

			// Check if the user has already selected a line spacing
			if (Designer.Data.SelectedLineSpacing == null) {

				selectedLineSpacing = Designer.Data.SelectedLineSpacing = '100';

			} else {

				selectedLineSpacing = Designer.Data.SelectedLineSpacing;
			}

			html +=       '<td colspan="2">';
			html +=         '<select name="stickylabel_designer[preview][spacing]" id="stickylabel_designer_preview_spacing" onchange="Designer.Data.SelectedLineSpacing = this.options[this.selectedIndex].value; Designer.LivePreview(this);">';
			html +=           '<option value="75"' + (selectedLineSpacing == '75' ? ' selected="selected"' : '') + '>75%</option>';
			html +=           '<option value="80"' + (selectedLineSpacing == '80' ? ' selected="selected"' : '') + '>80%</option>';
			html +=           '<option value="85"' + (selectedLineSpacing == '85' ? ' selected="selected"' : '') + '>85%</option>';
			html +=           '<option value="90"' + (selectedLineSpacing == '90' ? ' selected="selected"' : '') + '>90%</option>';
			html +=           '<option value="95"' + (selectedLineSpacing == '95' ? ' selected="selected"' : '') + '>95%</option>';
			html +=           '<option value="100"' + (selectedLineSpacing == '100' ? ' selected="selected"' : '') + '>100%</option>';
			html +=           '<option value="110"' + (selectedLineSpacing == '110' ? ' selected="selected"' : '') + '>110%</option>';
			html +=           '<option value="120"' + (selectedLineSpacing == '120' ? ' selected="selected"' : '') + '>120%</option>';
			html +=           '<option value="130"' + (selectedLineSpacing == '130' ? ' selected="selected"' : '') + '>130%</option>';
			html +=           '<option value="150"' + (selectedLineSpacing == '150' ? ' selected="selected"' : '') + '>150%</option>';
			html +=         '</select>';
			html +=        '</td>';

			html +=     '</tr>';

			// Output footer
			html +=   '</tbody>';
			html +=   '<tfoot>';

			var alignList = ['left', 'center', 'right', 'top', 'middle', 'bottom'];
			var alignIndexList = [1, 1, 1, 0, 0, 0];

			var align, alignIndex, shortAlign, textAlign;

			// Output text align panel
			html +=     '<tr class="align">';
			html +=       '<td class="spacer">&nbsp;</td>';
			html +=       '<td colspan="' + (type.styles.full_colours ? 4 : 3) + '">';
			html +=         '<table cellspacing="0" cellpadding="0">';
			html +=           '<tr>';

			for (i = 0; i < alignList.length; i ++) {

				align = alignList[i];
				alignIndex = alignIndexList[i];

				shortAlign = align.toUpperCase().charAt(0);
				textAlign = shortAlign + align.substr(1);

				html += '<td class="' + align + (shortAlign == Designer.Data.SelectedAlign.charAt(alignIndex) ? ' selected' : '') + '"><a href="/align/' + align + '" onclick="Designer.Actions.SelectAlign(this); Designer.LivePreview(this); return false;"><img src="' + Designer.Base + 'res/images/text-align/' + align + '.png" width="16" height="16" alt="' + textAlign + ' Align" title="' + textAlign + ' Align" /></a></td>';
			}

			html +=           '</tr>';
			html +=         '</table>';
			html +=       '</td>';
			html +=     '</tr>';

			if (Designer.Options.AllowLogo) {

				html +=     '<tr>';
				html +=       '<td class="logo" colspan="' + (type.styles.full_colours ? 5 : 4) + '">';
				html +=         '<input type="checkbox" class="checkbox" name="stickylabel_designer[preview][logo]" id="stickylabel_designer_preview_logo" value="1" onclick="if ( ! Designer.Actions.SelectLogo(this)) return false; Designer.LivePreview(this);"' + (Designer.Data.SelectedLogo ? ' checked="checked"' : '') + ' />';
				html +=         '<label for="stickylabel_designer_preview_logo">&nbsp;Insert Forever Living Products Eagle logo</label>';
				html +=       '</td>';
				html +=     '</tr>';
			}

			html +=     '<tr>';
			html +=       '<td class="options" colspan="2"><button onclick="Designer.Blocks.HideStaticOptions(); return false;">Change Options</button></td>';
			html +=       '<td class="update" colspan="' + (type.styles.full_colours ? 3 : 2) + '">' + (Designer.Constants.LivePreview ? '&nbsp;' : '<button onclick="Designer.UpdatePreview(); return false;">Update Preview</button>') + '</td>';
			html +=     '</tr>';
			html +=   '</tfoot>';
			html += '</table>';

			var cell = Designer.Data.TextPanelObject.childNodes[0];

			// Update the HTML for the text input panel
			cell.innerHTML = html;

			// Update instructions
			Designer.Blocks.SetColumnNames(5);

			// Raise OnGetTextPanel(...)
			Designer.Blocks.Events.OnGetTextPanel(type, cell);

			// Show the text input panel and hide the rest of the options
			Designer.Data.TextPanelObject.className += (Designer.Data.TextPanelObject.className.indexOf('text-visible') < 0 ? ' text-visible' : '');

			Designer.Blocks.ShowStaticOptions();

			// Load predefined Designer setup, if any
			Designer.Setup.LoadPredefined(type);

			if (Designer.Options.AllowLogo) {

				var logoState = $('stickylabel_designer_preview_logo');
				var logoStateLabel = logoState.nextSibling;

				var fixedInRange = false;

				// Loop all text rows and search for fixed
				for (var i = 0; i < type.text.rows; i ++) {

					// Check if the user has already entered data for this row
					if (typeof (Designer.Data.TextPanelData[i]) != 'undefined') {

						if (Designer.Data.TextPanelData[i].fixed) {

							fixedInRange = true;

							break;
						}
					}
				}

				if (( ! fixedInRange) &&
					(logoState.checked)) {

					logoState.checked = false;

					Designer.Actions.SelectLogo(logoState);

				} else if ((fixedInRange) &&
						   ( ! logoState.checked)) {

					logoState.checked = true;

					Designer.Actions.SelectLogo(logoState);
				}

				// LEN('Independent Distributor') == 23
				if (type.text.columns < 23) {

					logoState.checked = false;

					Designer.Actions.SelectLogo(logoState);

					logoState.disabled = true;
					logoState.setAttribute('disabled', true);

					logoStateLabel.disabled = true;
					logoStateLabel.setAttribute('disabled', true);

				} else {

					logoState.disabled = false;
					logoState.removeAttribute('disabled');

					logoStateLabel.disabled = false;
					logoStateLabel.removeAttribute('disabled');
				}
			}

			// Focus and select the text in the first line
			var firstInput = cell.getElementsByTagName('input')[0];

			try {

				firstInput.focus();
				firstInput.select();

			} catch (e) {}
		},

		// Builds the HTML code for the price panel
		GetPrice : function() {

			var cell = Designer.Data.PricePanelObject.childNodes[0];
			var productPrices = $('productPrices');

			// Remove any content from the price panel cell
			cell.innerHTML = '<em>Updating price, please wait...</em>';
			productPrices.style.visibility = 'hidden';
			// Show the loading indicator inside the cell
			cell.className += (cell.className.indexOf('loading') < 0 ? ' loading' : '');
			// Show the price panel
			Designer.Data.PricePanelObject.className = Designer.Data.PricePanelObject.className.replace('invisible', '');

			// Disable ZC interface
			Designer.ZC.EnableInterface(false);

			var params = Designer.GetParams(false);

			// Parse ZC quantity
			var quantity = parseInt(Designer.ZC.Data.QuantityBox.value);

			if ((isNaN(quantity)) ||
				(quantity < 1)) {

				quantity = 1;
			}

			var request;

			// Abort all pending and active requests
			for (var i = 0; i < Designer.Data.AjaxPriceRequests.length; i ++) {

				// Get a reference to the current AJAX request
				request = Designer.Data.AjaxPriceRequests[i];

				// If the request is pending/active, abort it
				if ( ! request.aborted) {

					request.aborted = true;

					// Make sure abort(...) is defined before invoking
					if (typeof (request.ajaxRequest.transport.abort) == 'function') {

						try { request.ajaxRequest.transport.abort } catch (e) {};
					}

					delete request.ajaxRequest;
				}
			}

			// Generate a new AJAX request and add it to the table
			request = {
				aborted : false,
				ajaxRequest : null
			};

			Designer.Data.AjaxPriceRequests[Designer.Data.AjaxPriceRequests.length] = request;

			// Make an AJAX request to the price calculation page
			request.ajaxRequest = new Ajax.Request(
				Designer.Base + 'service.stickylabel_pricecalc.php?' + params,
				{
					method : 'get',

					// On sucess, make sure the response is valid
					onSuccess : function(xmlHttp) {

						if (request.aborted) return false;

						var json;

						// Remove the loading indicator
						cell.className = cell.className.replace('loading', '');

						if ((typeof (xmlHttp.responseText) == 'string') &&
							(xmlHttp.responseText.length > 3) &&
							(xmlHttp.responseText.substr(0, 3) == 'OK:')) {

							try {

								eval('json = ' + xmlHttp.responseText.substr(3));

								// Update the HTML for the price panel
								cell.innerHTML = '<big>Total &pound;<strong>' + json.priceFormatted + '</strong></big>';

								productPrices.firstChild.innerHTML = '&pound;' + json.priceFormatted;
								productPrices.style.visibility = 'visible';

								// Check if we have any server messages to display
								if (typeof (json.messages) == 'object') {

									var index = 0;

									// Loop every message and output only string properties
									for (var key in json.messages) {

										if (typeof (json.messages[key]) == 'string') {

											if (index == 0) {

												cell.innerHTML += '<hr />';

											} else {

												cell.innerHTML += '<br />';
											}

											cell.innerHTML += '<small class="' + key + '">' + json.messages[key] + '</small>';

											index ++;
										}
									}

									// Update quantity description text
									var typeNum = parseInt(json.type);

									if (isNaN(typeNum)) {

										Designer.ZC.Data.QuantityText.innerHTML = '<br />= <strong>' + quantity + '</strong> ' + json.type;

									} else {

										Designer.ZC.Data.QuantityText.innerHTML = '<br />x ' + json.type + ' = <strong>' + (typeNum * quantity) + '</strong> labels';
									}

									Designer.ZC.Data.QuantityText.style.visibility = 'visible';

									// Update the product ID
									Designer.ZC.Data.ProductIdField.value = json.productId;

									// Enable and show fields from Zen
									Designer.ZC.EnableInterface(true);
								}

							} catch (e) {}

							return true;
						}

						// Ooppsy, something went wrong
						if (confirm('There was an error updating the price. Try again?\n\n--- Server Message: --------\n' + xmlHttp.responseText)) {

							return Designer.Blocks.GetPrice();
						}

						// Update the HTML for the price panel
						cell.innerHTML = '<big>Total &pound;<strong>N/A</strong> / <a href="/update" onclick="Designer.Blocks.GetPrice(); return false;">Update Price</a></big>';

						return false;
					},

					// Ooppsy, something went wrong
					onFailure : function(xmlHttp) {

						if (request.aborted) return false;

						// Remove the loading indicator
						cell.className = cell.className.replace('loading', '');

						if (confirm('An error has occured while updating the price. Try again?')) {

							return Designer.Blocks.GetPrice();
						}

						// Update the HTML for the price panel
						cell.innerHTML = '<big>Total &pound;<strong>N/A</strong> / <a href="/update" onclick="Designer.Blocks.GetPrice(); return false;">Update Price</a></big>';

						return false;
					},

					// Ooppsy, something went terribly wrong
					onException : function(xmlHttp, e) {

						if (request.aborted) return false;

						// Remove the loading indicator
						cell.className = cell.className.replace('loading', '');

						if (confirm('A fatal error has occured while updating the price. Try again?\n\n--- Error Message: --------\n' + e)) {

							return Designer.Blocks.GetPrice();
						}

						// Update the HTML for the price panel
						cell.innerHTML = '<big>Total &pound;<strong>N/A</strong> / <a href="/update" onclick="Designer.Blocks.GetPrice(); return false;">Update Price</a></big>';

						return false;
					}
				}
			);
		},

		// Shows and updated the static options
		ShowStaticOptions : function() {

			// Get a reference to the TBODY of the host object
			var container = Designer.Data.HostObject.childNodes[0].getElementsByTagName('tbody')[0];

			// Hide the first row that contains the dynamic options
			container.childNodes[0].className += (container.childNodes[0].className.indexOf('invisible') < 0 ? ' invisible' : '');

			// Show the second row that contains the static options
			container.childNodes[1].className = container.childNodes[1].className.replace('invisible', '');

			// Update static options
			container.childNodes[1].childNodes[0].innerHTML = Designer.Data.SelectedShape.innerHTML;
			container.childNodes[1].childNodes[1].innerHTML = Designer.Data.SelectedType.innerHTML;
			container.childNodes[1].childNodes[2].innerHTML = Designer.Data.SelectedSize.innerHTML;
			container.childNodes[1].childNodes[3].innerHTML = Designer.Data.SelectedStylesLabel.innerHTML;
			container.childNodes[1].childNodes[4].innerHTML = Designer.Data.SelectedStylesPrint.innerHTML;

			Designer.Data.StaticOptionsVisible = true;
		},

		// Hides and erased the static options
		HideStaticOptions : function() {

			// Get a reference to the TBODY of the host object
			var container = Designer.Data.HostObject.childNodes[0].getElementsByTagName('tbody')[0];

			// Show the first row that contains the dynamic options
			container.childNodes[0].className = container.childNodes[0].className.replace('invisible', '');

			// Hide the second row that contains the static options
			container.childNodes[1].className += (container.childNodes[1].className.indexOf('invisible') < 0 ? ' invisible' : '');

			// Erase static options
			container.childNodes[1].childNodes[0].innerHTML =
			container.childNodes[1].childNodes[1].innerHTML =
			container.childNodes[1].childNodes[2].innerHTML =
			container.childNodes[1].childNodes[3].innerHTML =
			container.childNodes[1].childNodes[4].innerHTML = '&nbsp;';

			Designer.Data.StaticOptionsVisible = false;
		},

		SetColumnNames : function(index) {

			var columns = [
				$('stickylabel_designer_column_0'),
				$('stickylabel_designer_column_1'),
				$('stickylabel_designer_column_2'),
				$('stickylabel_designer_column_3'),
				$('stickylabel_designer_column_4')
			];
			var columnNames = Designer.Events.OnGetTableColumnNames();

			for (var i = 0; i < 5; i ++) {

				if (i < index) {

					if (typeof (columnNames[i]) == 'string') {

						columns[i].innerHTML = columnNames[i];

					} else {

						columns[i].innerHTML = columnNames[i][0];
					}

				} else if (i == index) {

					if (typeof (columnNames[i]) == 'string') {

						columns[i].innerHTML = '<strong>' + columnNames[i] + '</strong>';

					} else {

						columns[i].innerHTML = '<strong>' + columnNames[i][1] + '</strong>';
					}

				} else {

					columns[i].innerHTML = '&nbsp';
				}
			}
		}
	},

	// Actions namespace
	//     Contains logic that handles designer actions, e.g. select shape
	Actions : {

		// Called when the user selects a shape from the list
		//     shape - DOM anchor element that contains information about the selected shape
		SelectShape : function(shape) {

			// Remove focus from element
			shape.blur();

			// Hide tooltip
			Designer.Tooltip.Hide();

			var selectedRow;
			var selectedCell;

			// Check if we have a selection defined
			if (Designer.Data.SelectedShape != null) {

				// Check if the shape is already selected
				if (Designer.Data.SelectedShape == shape) {

					return false;
				}

				selectedRow = Designer.Data.SelectedShape.parentNode.parentNode;
				selectedRow.className = selectedRow.className.replace('selected', '');

				selectedCell = selectedRow.getElementsByTagName('img')[1];
				selectedCell.src = selectedCell.src.replace('shape-selected.png', 'blank.gif');
				selectedCell.alt = selectedCell.title = '';
			}

			selectedRow = shape.parentNode.parentNode;
			selectedRow.className += ' selected';

			selectedCell = selectedRow.getElementsByTagName('img')[1];
			selectedCell.src = selectedCell.src.replace('blank.gif', 'shape-selected.png');
			selectedCell.alt = selectedCell.title = 'Selected';

			// Save the new selection
			Designer.Data.SelectedShape = shape;

			// Erase progress
			Designer.Data.SelectedType = null;
			Designer.Data.SelectedSize = null;
			Designer.Data.SelectedStylesLabel = null;
			Designer.Data.SelectedStylesPrint = null;

			// Rebuilt the 'Type' block based on the new shape
			Designer.Blocks.GetType();

			// Update the preview image to reflect the new selection
			Designer.OnChange();

			return true;
		},

		// Called when the user selects a type from the list
		//     type - DOM anchor element that contains information about the selected type
		SelectType : function(type) {

			// Remove focus from element
			type.blur();

			// Hide tooltip
			Designer.Tooltip.Hide();

			var selectedRow;
			var selectedCell;

			// Check if we have a selection defined
			if (Designer.Data.SelectedType != null) {

				// Check if the type is already selected
				if (Designer.Data.SelectedType == type) {

					return false;
				}

				selectedRow = Designer.Data.SelectedType.parentNode.parentNode;
				selectedRow.className = selectedRow.className.replace('selected', '');

				selectedCell = selectedRow.getElementsByTagName('img')[0];
				selectedCell.src = selectedCell.src.replace('type-selected.png', 'blank.gif');
				selectedCell.alt = selectedCell.title = '';
			}

			selectedRow = type.parentNode.parentNode;
			selectedRow.className += ' selected';

			selectedCell = selectedRow.getElementsByTagName('img')[0];
			selectedCell.src = selectedCell.src.replace('blank.gif', 'type-selected.png');
			selectedCell.alt = selectedCell.title = 'Selected';

			// Save the new selection
			Designer.Data.SelectedType = type;

			// Erase progress
			Designer.Data.SelectedSize = null;
			Designer.Data.SelectedStylesLabel = null;
			Designer.Data.SelectedStylesPrint = null;

			// Rebuilt the 'Size' block based on the new type
			Designer.Blocks.GetSize();

			// Update the preview image to reflect the new selection
			Designer.OnChange();

			return true;
		},

		// Called when the user selects a size from the list
		//     size - DOM anchor element that contains information about the selected size
		SelectSize : function(size) {

			// Remove focus from element
			size.blur();

			// Hide tooltip
			Designer.Tooltip.Hide();

			var selectedRow;
			var selectedCell;

			// Check if we have a selection defined
			if (Designer.Data.SelectedSize != null) {

				// Check if the size is already selected
				if (Designer.Data.SelectedSize == size) {

					return false;
				}

				selectedRow = Designer.Data.SelectedSize.parentNode.parentNode;
				selectedRow.className = selectedRow.className.replace('selected', '');

				selectedCell = selectedRow.getElementsByTagName('img')[0];
				selectedCell.src = selectedCell.src.replace('size-selected.png', 'blank.gif');
				selectedCell.alt = selectedCell.title = '';
			}

			selectedRow = size.parentNode.parentNode;
			selectedRow.className += ' selected';

			selectedCell = selectedRow.getElementsByTagName('img')[0];
			selectedCell.src = selectedCell.src.replace('blank.gif', 'size-selected.png');
			selectedCell.alt = selectedCell.title = 'Selected';

			// Save the new selection
			Designer.Data.SelectedSize = size;

			// Erase progress
			Designer.Data.SelectedStylesLabel = null;
			Designer.Data.SelectedStylesPrint = null;

			// Rebuilt the 'Label Colour' block based on the new size
			Designer.Blocks.GetStylesLabel();

			// Update the preview image to reflect the new selection
			Designer.OnChange();

			return true;
		},

		// Called when the user selects a label style from the list
		//     labelStyle - DOM anchor element that contains information about the selected label style
		SelectLabelStyle : function(labelStyle) {

			// Remove focus from element
			labelStyle.blur();

			// Hide tooltip
			Designer.Tooltip.Hide();

			var selectedRow;
			var selectedCell;

			// Check if we have a selection defined
			if (Designer.Data.SelectedStylesLabel != null) {

				// Check if the label style is already selected
				if (Designer.Data.SelectedStylesLabel == labelStyle) {

					return false;
				}

				selectedRow = Designer.Data.SelectedStylesLabel.parentNode.parentNode;
				selectedRow.className = selectedRow.className.replace('selected', '');

				selectedCell = selectedRow.getElementsByTagName('img')[1];
				selectedCell.src = selectedCell.src.replace('label-selected.png', 'blank.gif');
				selectedCell.alt = selectedCell.title = '';
			}

			selectedRow = labelStyle.parentNode.parentNode;
			selectedRow.className += ' selected';

			selectedCell = selectedRow.getElementsByTagName('img')[1];
			selectedCell.src = selectedCell.src.replace('blank.gif', 'label-selected.png');
			selectedCell.alt = selectedCell.title = 'Selected';

			// Save the new selection
			Designer.Data.SelectedStylesLabel = labelStyle;

			// Erase progress
			Designer.Data.SelectedStylesPrint = null;

			// Rebuilt the 'Print Colour' block based on the label style
			Designer.Blocks.GetStylesPrint();

			// Update the preview image to reflect the new selection
			Designer.OnChange();

			return true;
		},

		// Called when the user selects a print style from the list
		//     printStyle - DOM anchor element that contains information about the selected print style
		SelectPrintStyle : function(printStyle) {

			// Remove focus from element
			printStyle.blur();

			// Hide tooltip
			Designer.Tooltip.Hide();

			var selectedRow;
			var selectedCell;

			// Check if we have a selection defined
			if (Designer.Data.SelectedStylesPrint != null) {

				// Check if the print style is already selected
				if (Designer.Data.SelectedStylesPrint == printStyle) {

					return false;
				}

				selectedRow = Designer.Data.SelectedStylesPrint.parentNode.parentNode;
				selectedRow.className = selectedRow.className.replace('selected', '');

				selectedCell = selectedRow.getElementsByTagName('img')[1];

				if (selectedCell) {

					selectedCell.src = selectedCell.src.replace('print-selected.png', 'blank.gif');
					selectedCell.alt = selectedCell.title = '';
				}
			}

			selectedRow = printStyle.parentNode.parentNode;
			selectedRow.className += ' selected';

			selectedCell = selectedRow.getElementsByTagName('img')[1];

			if (selectedCell) {

				selectedCell.src = selectedCell.src.replace('blank.gif', 'print-selected.png');
				selectedCell.alt = selectedCell.title = 'Selected';
			}

			// Save the new selection
			Designer.Data.SelectedStylesPrint = printStyle;

			// Update the preview image to reflect the new selection
			Designer.OnChange();

			return true;
		},

		// Called when the user selects a horizontal/vertical align from the list
		//     obj - DOM anchor element that contains information about the selected alignment
		SelectAlign : function(obj) {

			// Get the selected short align (last part of the URL)
			var selectedShortAlign = obj.href.split('/');
			selectedShortAlign = selectedShortAlign[selectedShortAlign.length - 1];
			selectedShortAlign = selectedShortAlign.toUpperCase().charAt(0);

			// Check if it is not already selected
			if (Designer.Data.SelectedAlign.indexOf(selectedShortAlign) >= 0) {

				return false;
			}

			// Save new selection
			if ((selectedShortAlign == 'L') ||
				(selectedShortAlign == 'C') ||
				(selectedShortAlign == 'R')) {

				Designer.Data.SelectedAlign = Designer.Data.SelectedAlign.charAt(0) + selectedShortAlign;

			} else if ((selectedShortAlign == 'T') ||
					   (selectedShortAlign == 'M') ||
					   (selectedShortAlign == 'B')) {

				Designer.Data.SelectedAlign = selectedShortAlign + Designer.Data.SelectedAlign.charAt(1);
			}

			// Remove old selection and highlight active
			var links = obj.parentNode.parentNode.getElementsByTagName('a');
			var linkShortAlign;

			for (var i = 0; i < links.length; i ++) {

				cellShortAlign = links[i].href.split('/');
				cellShortAlign = cellShortAlign[cellShortAlign.length - 1];
				cellShortAlign = cellShortAlign.toUpperCase().charAt(0);

				if (Designer.Data.SelectedAlign.indexOf(cellShortAlign) < 0) {

					links[i].parentNode.className = links[i].parentNode.className.replace('selected', '');

				} else {

					links[i].parentNode.className += (links[i].parentNode.className.indexOf('selected') > 0 ? '' : ' selected');
				}
			}

			return true;
		},

		// Called when the user selects/deselects 'Insert FLP Eagle logo'
		//     obj - DOM element that contains information about the state
		SelectLogo : function(obj) {

			var markerFound = false;

			// Get the selected type ID (last part of the URL)
			var selectedTypeId = Designer.Data.SelectedSize.href.split('/');
			selectedTypeId = parseInt(selectedTypeId[selectedTypeId.length - 1]);

			// Get the selected type by ID
			var type = Designer.Model.types[selectedTypeId];

			var container;
			var i;

			for (i = type.text.rows; i < Designer.Data.TextPanelData.length; i ++) {

				Designer.Data.TextPanelData[i].fixed = false;
			}

			// Loop all text rows and search for marker
			for (i = 0; i < type.text.rows; i ++) {

				// Check if the user has already entered data for this row
				if (typeof (Designer.Data.TextPanelData[i]) != 'undefined') {

					container = $('stickylabel_designer_preview_text_' + i);

					if (Designer.Data.TextPanelData[i].fixed) {

						Designer.Data.TextPanelData[i].fixed = false;

						container.className = '';

						container.readOnly = false;
						container.removeAttribute('readonly');
					}

					if (( ! markerFound) &&
						('independent distributor' == Designer.Data.TextPanelData[i].text.substr(0, type.text.columns).toLowerCase())) {

						markerFound = true;

						if (obj.checked) {

							Designer.Data.TextPanelData[i].fixed = true;

							container.className = 'fixed';

							container.readOnly = true;
							container.setAttribute('readonly', true);
						}
					}
				}
			}

			if ((obj.checked) &&
				( ! markerFound)) {

				if (Designer.Data.SuspendUpdate) {

					return false;
				}

				if ( ! confirm('If you would like to insert the Forever Living Products Eagle logo the words \'Independent Distributor\' must appear on your label.\n'
							 + 'Select OK and enter the line number that you would like to contain these words.\n'
							 + 'Select Cancel to quit.')) {

					return false;

				} else {

					var promptResult = prompt('Please enter the line number [1 - ' + type.text.rows + '] that will contain the words \'Independent Distributor\'.', 2);

					if (( ! promptResult) ||
						(trim(promptResult).length < 1)) {

						return false;
					}

					index = parseInt(promptResult);

					if ((isNaN(index)) ||
						(index < 1) ||
						(index > type.text.rows)) {

						alert('The selection you have made (\'' + promptResult + '\') is not valid.');

						return false;
					}

					container = $('stickylabel_designer_preview_text_' + (index - 1));
					container.value = 'Independent Distributor';
					container.onchange();

					Designer.Data.TextPanelData[index - 1].fixed = true;

					container.className = 'fixed';

					container.readOnly = true;
					container.setAttribute('readonly', true);
				}
			}

			Designer.Data.SelectedLogo = obj.checked;

			return true;
		},

		// Updates the text panel data object with the new text entered in the given line
		//     obj - the parent object that triggered the update
		UpdateTextLine : function(obj) {

			var index = obj.name.split('[');
			index = parseInt(index[index.length - 1]);

			if (typeof (obj._value) == 'undefined') {

				if ((typeof (Designer.Data.TextPanelData[index]) == 'object') &&
					(Designer.Data.TextPanelData[index].text)) {

					obj._value = Designer.Data.TextPanelData[index].text;

				} else {

					obj._value = '';
				}
			}

			if (obj.value != obj._value) {

				obj._value = obj.value;

				Designer.Data.TextPanelData[index].text = obj.value;

				Designer.LivePreview(obj);
			}
		}
	},

	// ZenCart namespace
	//     Handles operations on the ZC interface
	ZC : {

		// Data namespace
		//     Contains the state of the ZC interface
		Data : {

			// The DOM element that hosts the interface
			HostObject : null,

			// The DOM element that contains the quantity input box
			QuantityBox : null,

			// The DOM element that contains the quantity description text
			QuantityText : null,

			// The DOM element that contains the product ID being added to the cart
			ProductIdField : null,

			// The DOM element that contains the Add to Cart button
			AddToCartButton : null,

			// The DOM element that contains the Add to Cart image
			AddToCartImage : null
		},

		// Enables or disables the ZC interface controls
		//     enabled - true to enabled the controls, false to disabled them
		EnableInterface : function(enabled) {

			if (typeof (enabled) == 'boolean' && enabled) {

				// Disable and hide fields from Zen
				Designer.ZC.Data.QuantityBox.removeAttribute('disabled');
				Designer.ZC.Data.AddToCartButton.style.display = '';

				Designer.ZC.Data.QuantityText.style.visibility = 'visible';
				Designer.ZC.Data.AddToCartImage.style.display = 'none';

			} else {

				// Disable and hide fields from Zen
				Designer.ZC.Data.QuantityBox.setAttribute('disabled', true);
				Designer.ZC.Data.AddToCartButton.style.display = 'none';

				Designer.ZC.Data.QuantityText.style.visibility = 'hidden';
				Designer.ZC.Data.AddToCartImage.style.display = '';
			}
		},

		// Entry point for the ZC interface
		//     hostObj - DOM element that hosts the ZC interface
		Run : function(hostObj) {

			// Make sure we have hostObj defined
			if (typeof (hostObj) != 'object') {

				return false;
			}

			Designer.ZC.Data.HostObject = hostObj;

			// Assign data objects
			var inputs = hostObj.getElementsByTagName('input');

			Designer.ZC.Data.QuantityBox = inputs[0];
			Designer.ZC.Data.ProductIdField = inputs[1];
			Designer.ZC.Data.AddToCartButton = inputs[2];

			// Replace quantity box with hidden field
			var staticQuantity = document.createElement('input');
			/*@cc_on staticQuantity = document.createElement('<input type="hidden">'); @*/

			staticQuantity.type = "hidden";
			staticQuantity.name = Designer.ZC.Data.QuantityBox.name;
			staticQuantity.value = 1;

			Designer.ZC.Data.QuantityBox.name += '_';

			// Attach quantity box hooks
			Designer.ZC.Data.QuantityBox._value = parseInt(Designer.ZC.Data.QuantityBox.value);
			Designer.ZC.Data.QuantityBox._tid = 0;

			if ((isNaN(Designer.ZC.Data.QuantityBox._value)) ||
				(Designer.ZC.Data.QuantityBox._value < 1)) {

				Designer.ZC.Data.QuantityBox._value = 1;
			}

			// Make sure we catch all events that can update the contents of the field
			Designer.ZC.Data.QuantityBox.onkeydown = Designer.ZC.Data.QuantityBox.onkeyup = Designer.ZC.Data.QuantityBox.onkeypress = function() {

				// Stop pending timeout
				if (Designer.ZC.Data.QuantityBox._tid) {

					window.clearTimeout(Designer.ZC.Data.QuantityBox._tid);

					Designer.ZC.Data.QuantityBox._tid = 0;
				}

				// Check if the new INT value is different
				var newValue = parseInt(this.value);

				if (( ! isNaN(newValue)) &&
					(newValue > 0) &&
					(newValue != this._value)) {

					this._value = newValue;

					this._tid = window.setTimeout(function() {

						Designer.ZC.Data.QuantityBox._tid = 0;

						Designer.UpdatePrice();

					}, 1000);
				}
			};

			// Attach Add to Cart button hooks
			Designer.ZC.Data.AddToCartButton.onclick = function() {

				// Update container with full label parameters
				Designer.Data.ParamsContainerObject.value = Designer.GetParams(true);

				return true;
			};

			// Insert the quantity description text element
			Designer.ZC.Data.QuantityText = document.createElement('span');

			Designer.ZC.Data.QuantityText.innerHTML = '<br />&nbsp;';

			// Insert the Add to Cart image that is used to disable the button
			Designer.ZC.Data.AddToCartImage = document.createElement('img');

			Designer.ZC.Data.AddToCartImage.src = Designer.ZC.Data.AddToCartButton.src;

			Designer.ZC.Data.AddToCartImage.style.cursor = 'not-allowed';

			Designer.ZC.Data.AddToCartImage.style.filter = 'alpha(opacity=75)';
			Designer.ZC.Data.AddToCartImage.style.opacity = 0.75;
			Designer.ZC.Data.AddToCartImage.style.MozOpacity = 0.75;

			// Disable interface and add the elements to the DOM
			Designer.ZC.EnableInterface(false);

			hostObj.insertBefore(Designer.ZC.Data.QuantityText, Designer.ZC.Data.QuantityBox.nextSibling);
			hostObj.insertBefore(Designer.ZC.Data.AddToCartImage, hostObj.childNodes[hostObj.childNodes.length - 1]);
			hostObj.appendChild(staticQuantity);

			return true;
		}
	},

	// Tooltip namespace
	//     Handles the display of tooltips
	Tooltip : {

		// Shows the tooltip
		//     obj - DOM element that contains the text
		//     text - If set, overrides the DOM element's text
		Show : function(obj, text) {

			var objCopy = obj;
			var top = 0;
			var left = 0;

			do {

				top += (objCopy.offsetTop || 0);
				left += (objCopy.offsetLeft || 0);

				objCopy = objCopy.offsetParent;

			} while (objCopy);

			Designer.Data.TooltipObject = document.createElement('span');
			Designer.Data.TooltipObject.className = 'tooltip';

			Designer.Data.TooltipObject.innerHTML = (typeof (text) == 'string' ? text : obj.alt);
			Designer.Data.TooltipObject.style.top = '-1000px';
			Designer.Data.TooltipObject.style.left = '-1000px';

			Designer.Data.HostObject.appendChild(Designer.Data.TooltipObject);

			Designer.Data.TooltipObject.style.top = (top + ((obj.height || obj.offsetHeight) - Designer.Data.TooltipObject.offsetHeight) / 2)+ 'px';
			Designer.Data.TooltipObject.style.left = (left + (obj.width || obj.offsetWidth) + 2) + 'px';
		},

		// Hides the tooltip
		Hide : function() {

			// Make sure the tooltip is initialised
			if (Designer.Data.TooltipObject != null) {

				Designer.Data.HostObject.removeChild(Designer.Data.TooltipObject);

				Designer.Data.TooltipObject = null;
			}
		},

		// Shows a 'Price Guide' tooltip for the selected size
		//     obj - DOM element that contains the selected size
		ShowPriceGuide : function(obj) {

			// Get the selected type ID (last part of the URL)
			var typeId = obj.href.split('/');
			typeId = parseInt(typeId[typeId.length - 1]);

			var price;
			var lowestPrice = 0;

			for (var i = 0; i < Designer.Model.types[typeId].price.length; i ++) {

				price = Designer.Model.types[typeId].price[i].price;

				lowestPrice = (lowestPrice ? Math.min(price, lowestPrice) : price);
			}

			if (lowestPrice) {

				var html = '';

				html += '<div style="text-align: center;">';
				html +=   '<div>';
				html +=     'Price for ' + obj.innerHTML + '<br />starting as low as';
				html +=   '</div>';
				html +=   '<div style="font-size: 135%; padding-top: 8px;">';
				html +=     '<strong>&pound;' + lowestPrice + '</strong>';
				html +=   '</div>';
				html += '</div>';

				Designer.Tooltip.Show(obj, html);

				return true;
			}

			return false;
		}
	},

	// Images namespace
	//     Handles the pre-loading of images
	Images : {

		// Array with all files that will be pre-loaded
		Files : ['shape-selected.png', 'type-selected.png', 'size-selected.png', 'label-selected.png', 'print-selected.png',
				 'help/background-tile.png', 'help/popup-bottom.png', 'help/popup-close.png', 'help/popup-middle.png', 'help/popup-top.png', 'help/popup-ie6bottom.png'],

		// Outputs HTML code that pre-loads the defined images
		Preload : function() {

			for (var i = 0; i < Designer.Images.Files.length; i ++) {

				// Output a hidden, absolutely positioned image that is not visible to the user
				document.writeln('<img src="' + Designer.Base + 'res/images/' + Designer.Images.Files[i] + '" style="visibility: hidden; position: absolute; top: 0; left: 0;" />');
			}

			var safeName;

			// Preload label styles
			for (var key in Designer.Model.styles.label) {

				// Build the 'safe' name of the current label style
				safeName = Designer.Model.styles.label[key].name.toLowerCase().replace(/[^A-Z0-9]/ig, '-');

				// Output a hidden, absolutely positioned image that is not visible to the user
				document.writeln('<img src="' + Designer.Base + 'res/images/styles/label/' + safeName + '.png" style="visibility: hidden; position: absolute; top: 0; left: 0;" />');
			}

			var printStyle;

			// Preload print styles
			for (var key in Designer.Model.styles.print) {

				// Get a reference to the current print style
				printStyle = Designer.Model.styles.print[key];

				// Print styles available only when Full colour is selected are not preloaded
				if (printStyle.only_full_colour) {

					continue;
				}

				// Build the 'safe' name of the current print style
				safeName = printStyle.name.toLowerCase().replace(/[^A-Z0-9]/ig, '-');

				// Output a hidden, absolutely positioned image that is not visible to the user
				document.writeln('<img src="' + Designer.Base + 'res/images/styles/print/' + safeName + '.png" style="visibility: hidden; position: absolute; top: 0; left: 0;" />');
			}
		}
	},

	// Handles changes that the user makes to the sticky label design
	OnChange : function() {

		Designer.UpdateTextPanel();

		Designer.UpdatePreview();

		if (Designer.Options.DynamicPricing) {

			Designer.UpdatePrice();
		}
	},

	// Returns a string with all label parameters
	GetParams : function(includeText) {

		var params = '';

		var type;
		var printStyle;

		// Append selected shape
		if (Designer.Data.SelectedShape != null)
		{
			// Get the selected shape (last part of the URL)
			var selectedShape = Designer.Data.SelectedShape.href.split('/');
			selectedShape = selectedShape[selectedShape.length - 1];

			params += ';shape:' + escape(selectedShape);
		}

		// Append selected type
		if (Designer.Data.SelectedType != null)
		{
			// Get the selected type (last part of the URL)
			var selectedType = Designer.Data.SelectedType.href.split('/');
			selectedType = selectedType[selectedType.length - 1];

			params += ';type:' + escape(selectedType);
		}

		// Append selected size
		if (Designer.Data.SelectedSize != null)
		{
			// Get the selected type ID (last part of the URL)
			var selectedTypeId = Designer.Data.SelectedSize.href.split('/');
			selectedTypeId = parseInt(selectedTypeId[selectedTypeId.length - 1]);

			// Get the selected type by ID
			type = Designer.Model.types[selectedTypeId];

			var width = type.size.width;
			var height = type.size.height;

			// If this is a circular shape, height = width
			if (height < 1) {

				height = width;
			}

			// Resize the image as well
			Designer.Data.PreviewImageObject.width = Math.round(width * Designer.Constants.Mm2PxFactor);
			Designer.Data.PreviewImageObject.height = Math.round(height * Designer.Constants.Mm2PxFactor);

			params += ';id:' + selectedTypeId
					+ ';width:' + width
					+ ';height:' + height;
		}
		// If we don't have size selected, use the default values
		else
		{
			// For circular shapes, height = width
			if (params.indexOf('shape:circular') > 0) {

				Designer.Data.PreviewImageObject.width = 240;
				Designer.Data.PreviewImageObject.height = 240;

			} else {

				Designer.Data.PreviewImageObject.width = 240;
				Designer.Data.PreviewImageObject.height = 120;
			}
		}

		// Append label style hex value and name
		if (Designer.Data.SelectedStylesLabel != null)
		{
			// Get the selected label style ID (last part of the URL)
			var selectedStylesLabelId = Designer.Data.SelectedStylesLabel.href.split('/');
			selectedStylesLabelId = parseInt(selectedStylesLabelId[selectedStylesLabelId.length - 1]);

			params += ';label:' + selectedStylesLabelId
					+ ';label-colour:' + Designer.Model.styles.label[selectedStylesLabelId].hex_value
					+ ';label-name:' + escape(Designer.Model.styles.label[selectedStylesLabelId].name);
		}

		// Append print style hex value and name
		if (Designer.Data.SelectedStylesPrint != null)
		{
			// Get the selected print style ID (last part of the URL)
			var selectedStylesPrintId = Designer.Data.SelectedStylesPrint.href.split('/');
			selectedStylesPrintId = selectedStylesPrintId[selectedStylesPrintId.length - 1];

			// Handle full-colour print style
			if (selectedStylesPrintId == 'FC') {

				printStyle = 'FC';

				params += ';print-colour:' + printStyle;

			} else {

				printStyle = Designer.Model.styles.print[parseInt(selectedStylesPrintId)];

				params += ';print:' + selectedStylesPrintId
						+ ';print-colour:' + printStyle.hex_value
						+ ';print-name:' + escape(printStyle.name);
			}
		}

		if (Designer.Options.DynamicPricing) {

			// Parse ZC quantity
			var quantity = parseInt(Designer.ZC.Data.QuantityBox.value);

			if ((isNaN(quantity)) ||
				(quantity < 1)) {

				quantity = 1;
			}

			params += ';quantity:' + quantity;
		}

		// Append selected font
		if ((includeText) &&
			(Designer.Data.SelectedFont != null)) {

			params += ';font:' + escape(Designer.Data.SelectedFont);
		}

		// Append selected line spacing
		if ((includeText) &&
			(Designer.Data.SelectedLineSpacing != null)) {

			params += ';line-spacing:' + escape(Designer.Data.SelectedLineSpacing);
		}

		// Append selected text alignment
		if ((includeText) &&
			(Designer.Data.SelectedAlign != null)) {

			params += ';align:' + escape(Designer.Data.SelectedAlign);
		}

		// Append 'Insert FLP Eagle logo' state
		if ((includeText) &&
			(Designer.Data.SelectedLogo != null)) {

			params += ';logo:' + (Designer.Data.SelectedLogo ? 1 : 0);
		}

		if ((includeText) &&
			(typeof (type) == 'object') &&
			(typeof (printStyle) != 'undefined')) {

			var typedText;
			var selectedStyle;
			var selectedSize;
			var selectedColour;

			// Loop all text rows and append values
			for (var i = 0; i < type.text.rows; i ++) {

				// Check if the user has entered data for this row
				if (typeof (Designer.Data.TextPanelData[i]) == 'undefined') {

					continue;
				}

				typedText = Designer.Data.TextPanelData[i].text;
				selectedStyle = Designer.Data.TextPanelData[i].style;
				selectedSize = Designer.Data.TextPanelData[i].size;
				selectedColour = Designer.Data.TextPanelData[i].colour;

				params += ';text[' + i + ']:'
						+ selectedStyle + ','
						+ selectedSize + ','
						+ (printStyle == 'FC' ? selectedColour : printStyle.hex_value) + ','
						+ escape(typedText.substr(0, type.text.columns));
			}
		}

		return params;
	},

	// Updates the preview image when the user changes the shape, type, size or style
	UpdatePreview : function() {

		// Suspend update if flag is set
		if (Designer.Data.SuspendUpdate) {

			return false;
		}

		// Check if any of the required components is missing
		if ((Designer.Data.SelectedShape == null) ||
			(Designer.Data.SelectedSize == null) ||
			(Designer.Data.SelectedStylesLabel == null)) {

			Designer.Data.PreviewImageObject.parentNode.parentNode.className += (Designer.Data.PreviewImageObject.parentNode.parentNode.className.indexOf('invisible') < 0 ? ' invisible' : '');

			return;
		}

		var params = Designer.GetParams(true);

		// Check if we need to render the image
		if (params != Designer.Data.LastPreviewParams)
		{
			var parentCell = Designer.Data.PreviewImageObject.parentNode;
			var parentRow = parentCell.parentNode;

			// Make sure the row is visible
			parentRow.className = parentRow.className.replace('invisible', '');
			parentCell.className += (parentCell.className.indexOf('loading') < 0 ? ' loading' : '');

			// Generate a preview image
			Designer.Data.PreviewImageObject.src = Designer.Base + 'service.stickylabel_preview.php?' + params;

			Designer.Data.PreviewImageObject.onload = function() {

				parentCell.className = parentCell.className.replace('loading', '');
			};

			Designer.Data.LastPreviewParams = params;
		}
	},

	// Updates the preview image when live preview is turned on
	//     obj - the parent object that triggered the update
	LivePreview : function(obj) {

		if ((typeof (obj) == 'object') &&
			(typeof (obj.tagName) == 'string') &&
			(obj.tagName.toLowerCase() == 'input') &&
			(obj.type.toLowerCase() == 'text')) {

			// Stop pending timeout
			if (obj._tid) {

				window.clearTimeout(obj._tid);

				obj._tid = 0;
			}

			obj._tid = window.setTimeout(function() {

				obj._tid = 0;

				Designer.UpdatePreview();

			}, 1000);

			return true;
		}

		if (Designer.Constants.LivePreview) {

			Designer.UpdatePreview();
		}
	},

	// Updates text input panel when the user changes the shape, type or size
	UpdateTextPanel : function() {

		// If no print style is selected, hide the text input panel
		if (Designer.Data.SelectedStylesPrint == null) {

			Designer.Data.TextPanelObject.className = Designer.Data.TextPanelObject.className.replace('text-visible', '');

		} else {

			// Rebuild the text input panel
			Designer.Blocks.GetTextPanel();
		}
	},

	// Updates the Total price panel
	UpdatePrice : function() {

		// If no print style is selected, hide the total price panel
		if (Designer.Data.SelectedStylesPrint == null) {

			// And hide the parent row
			Designer.Data.PricePanelObject.className += (Designer.Data.PricePanelObject.className.indexOf('invisible') < 0 ? ' invisible' : '');

			// Disable and hide fields from Zen
			Designer.ZC.EnableInterface(false);

		} else {

			// Rebuild the price panel
			Designer.Blocks.GetPrice();
		}
	},

	// Changes the index of the active step
	//     index - The new step index
	SetActiveStep : function(index) {

		Designer.Data.ActiveStep = index;

		if ((Designer.Help) &&
			(Designer.Data.Visible)) {

			Designer.Help.ShowInsructions(index);
		}
	},

	// Restores the state of the designer
	//     params - the parameters string that contains the state of the designer
	Restore : function(params) {

		var key, value, parts;
		var i, j;

		params = trim(params);

		// Make sure we have something to parse
		if ((params.length < 1) ||
			(params.indexOf(':') < 0)) {

			return false;
		}

		params = params.split(';');

		var paramsObj = {
			text : []
		};

		// Explode each query parameter to key => value pair
		for (i = 0; i < params.length; i ++) {

			params[i] = trim(params[i]).split(':');

			// Make sure we have exactly 2 items in the array
			if (params[i].length != 2) {

				continue;
			}

			key = params[i][0];
			value = params[i][1];

			// Match text[#] parameter
			if ('text[' == key.substr(0, 5)) {

				key = parseInt(key.replace('text[', '').replace(']', ''));

				// Explode and validate item parts
				parts = value.split(',');

				if (parts.length != 4) {

					continue;
				}

				paramsObj.text[key] = {

					style : parts[0],
					size : parts[1],
					colour : parts[2],
					text : unescape(parts[3])
				};

			  // Just append key => value pair
			} else {

				paramsObj[key] = unescape(value);
			}
		}

		Designer.Data.SuspendUpdate = true;

		var container = Designer.Data.HostObject.childNodes[0].getElementsByTagName('tbody')[0].childNodes[0].childNodes;
		var items;
		var status = true;

		// Restore shape selection
		status &= (typeof (paramsObj.shape) != 'undefined');

		if (status) {

			status = false;

			items = container[0].getElementsByTagName('a');

			for (i = 0; i < items.length; i ++) {

				if (items[i].href.indexOf('shape/' + paramsObj.shape) > 0) {

					items[i].onclick();

					status = true;

					break;
				}
			}
		}

		// Restore type selection
		status &= (typeof (paramsObj.type) != 'undefined') && ( ! Designer.Data.StaticOptionsVisible);

		if (status) {

			status = false;

			items = container[1].getElementsByTagName('a');

			for (i = 0; i < items.length; i ++) {

				if (items[i].href.indexOf('type/' + paramsObj.type) > 0) {

					items[i].onclick();

					status = true;

					break;
				}
			}
		}

		// Restore size selection
		status &= (typeof (paramsObj.id) != 'undefined') && ( ! Designer.Data.StaticOptionsVisible);

		if (status) {

			status = false;

			items = container[2].getElementsByTagName('a');

			for (i = 0; i < items.length; i ++) {

				if (items[i].href.indexOf('id/' + paramsObj.id) > 0) {

					items[i].onclick();

					status = true;

					break;
				}
			}
		}

		// Restore label colour selection
		status &= (typeof (paramsObj.label) != 'undefined') && ( ! Designer.Data.StaticOptionsVisible);

		if (status) {

			status = false;

			items = container[3].getElementsByTagName('a');

			for (i = 0; i < items.length; i ++) {

				if (items[i].href.indexOf('styles/label/' + paramsObj.label) > 0) {

					items[i].onclick();

					status = true;

					break;
				}
			}
		}

		// Restore print colour selection
		status &= (typeof (paramsObj.print) != 'undefined') && ( ! Designer.Data.StaticOptionsVisible);

		if (status) {

			status = false;

			items = container[4].getElementsByTagName('a');

			for (i = 0; i < items.length; i ++) {

				if (items[i].href.indexOf('styles/print/' + paramsObj.print) > 0) {

					items[i].onclick();

					status = true;

					break;
				}
			}
		}

		// Restore font style selection
		if ((typeof (paramsObj.font) != 'undefined') &&
			(Designer.Data.StaticOptionsVisible)) {

			container = $('stickylabel_designer_preview_font');

			for (i = 0; i < container.options.length; i ++) {

				if (container.options[i].value == paramsObj.font) {

					container.selectedIndex = i;
					container.onchange();

					break;
				}
			}
		}

		// Restore line spacing selection
		if ((typeof (paramsObj['line-spacing']) != 'undefined') &&
			(Designer.Data.StaticOptionsVisible)) {

			container = $('stickylabel_designer_preview_spacing');

			for (i = 0; i < container.options.length; i ++) {

				if (container.options[i].value == paramsObj['line-spacing']) {

					container.selectedIndex = i;
					container.onchange();

					status = true;

					break;
				}
			}
		}

		// Restore line spacing selection
		if ((typeof (paramsObj['align']) != 'undefined') &&
			(Designer.Data.StaticOptionsVisible)) {

			Designer.Data.SelectedAlign = paramsObj['align'];

			// Remove old selection and highlight active
			var links = Designer.Data.TextPanelObject.getElementsByTagName('tfoot')[0].getElementsByTagName('tr')[0].getElementsByTagName('a');
			var linkShortAlign;

			for (var i = 0; i < links.length; i ++) {

				cellShortAlign = links[i].href.split('/');
				cellShortAlign = cellShortAlign[cellShortAlign.length - 1];
				cellShortAlign = cellShortAlign.toUpperCase().charAt(0);

				if (Designer.Data.SelectedAlign.indexOf(cellShortAlign) < 0) {

					links[i].parentNode.className = links[i].parentNode.className.replace('selected', '');

				} else {

					links[i].parentNode.className += (links[i].parentNode.className.indexOf('selected') > 0 ? '' : ' selected');
				}
			}
		}

		// Restore text input panel
		if (paramsObj.text.length > 0)
		{
			var text;

			for (i = 0; i < paramsObj.text.length; i ++) {

				text = paramsObj.text[i];

				// Restore text
				container = $('stickylabel_designer_preview_text_' + i);
				container.value = text.text;
				container.onchange();

				// Restore style
				container = $('stickylabel_designer_preview_style_' + i);

				for (j = 0; j < container.options.length; j ++) {

					if (container.options[j].value == text.style) {

						container.selectedIndex = j;

						break;
					}
				}

				container.onchange();

				// Restore size
				container = $('stickylabel_designer_preview_size_' + i);

				for (j = 0; j < container.options.length; j ++) {

					if (container.options[j].value == text.size) {

						container.selectedIndex = j;

						break;
					}
				}

				container.onchange();

				// Restore colour
				container = $('stickylabel_designer_preview_colour_' + i);

				if (container != null) {

					for (j = 0; j < container.options.length; j ++) {

						if (container.options[j].value == text.colour) {

							container.selectedIndex = j;

							break;
						}
					}

					container.onchange();
				}
			}
		}

		// Restore logo selection
		if ((typeof (paramsObj['logo']) != 'undefined') &&
			(Designer.Options.AllowLogo) &&
			(Designer.Data.StaticOptionsVisible)) {

			container = $('stickylabel_designer_preview_logo');

			container.checked = (parseInt(paramsObj['logo']) > 0);
			container.onclick();
		}

		Designer.Data.SuspendUpdate = false;

		// Update the label preview
		Designer.UpdatePreview();

		// Raise the Restored flag
		Designer.Data.Restored = true;

		return true;
	},

	// Setup namespace
	//     Contains predefined Designer setup
	Setup : {

		// Loads predefined designer setup, if any
		//     type - Contains the type name of the selected label
		LoadPredefined : function(type) {

			// Build type name
			var typeName;

			if (type.type == 'badges') {

				typeName = 'Badges';

			} else if (type.type == 'cardsback') {

				typeName = 'CardsBack';

			} else if (type.type == 'cards') {

				typeName = 'BusinessCards';

			} else if (type.type == 'letterheads') {

				typeName = 'Letterheads';

			} else if (type.type == 'complimentslips') {

				typeName = 'ComplimentSlips';

			} else {

				typeName = 'Labels';
			}

			// Get the selected label style ID (last part of the URL)
			var selectedStylesLabelId = Designer.Data.SelectedStylesLabel.href.split('/');
			selectedStylesLabelId = parseInt(selectedStylesLabelId[selectedStylesLabelId.length - 1]);

			var labelStyle = Designer.Model.styles.label[selectedStylesLabelId];

			// Make sure we have a label style defined for the selected ID
			if (typeof (labelStyle) == 'object') {

				var setup = false;

				// Build the 'safe' name of the selected label style
				labelSafeName = labelStyle.name.replace(/[^A-Z0-9]/ig, '');

				// Check if we have predefined Designer setup for this type
				if (typeof (Designer.Setup[typeName]) == 'string') {

					setup = Designer.Setup[typeName];

				  // Check if we have predefined Designer setup for this label
				} else if ((typeof (Designer.Setup[typeName]) == 'object') &&
					(typeof (Designer.Setup[typeName][labelSafeName]) == 'string')) {

					setup = Designer.Setup[typeName][labelSafeName];
				}

				if (setup) {

					// Replace line markers
					var pattern = /\{line\s+(\d+)(?:\s+default\s+([^\}]+))*\}/i;
					var markers;
					var index, text, defaultText;

					while ((markers = pattern.exec(setup)) != null) {

						index = markers[1];
						text = '';

						if ((typeof (Designer.Data.TextPanelData[index]) == 'object') &&
							(Designer.Data.TextPanelData[index].text)) {

							text = escape(Designer.Data.TextPanelData[index].text);

						} else if (typeof (markers[2]) != 'undefined') {

							if (markers[2].indexOf('/') > 0) {

								defaultText = markers[2].split('/');

								if (Designer.Model.customer) {

									text = defaultText[1];

									var value;

									for (var key in Designer.Model.customer) {

										value = Designer.Model.customer[key];

										text = text.replace('@' + key + '@', escape(value));
									}

								} else {

									text = defaultText[0];
								}

							} else {

								text = markers[2];
							}
						}

						setup = setup.replace(markers[0], text);
					}

					// Update Designer with the prededined setup
					Designer.Restore(setup);
				}
			}
		}
	},

	// Events namespace
	//     Provides callbacks for extending the default behaviour
	Events : {

		// Returns the default column names for the main Designer table
		OnGetTableColumnNames : function() {

			return [
				['Shape<br />&nbsp;', 'Select your<br />label shape'],
				['Type<br />&nbsp;', 'Select your<br />label type'],
				['Size<br />&nbsp;', 'Select your<br />label size'],
				['Label<br />Colour', 'Select your<br />label colour'],
				['Print<br />Colour', 'Select your<br />print colour']
			];
		}
	},

	// Main entry point for the designer
	//     hostObj - DOM element that will host the HTML for the designer
	Run : function(hostObj) {

		// Make sure we have hostObj defined
		if (typeof (hostObj) != 'object') {

			return false;
		}

		Designer.Images.Preload();

		Designer.Data.HostObject = hostObj;

		// Get a reference to the container item
		Designer.Data.ParamsContainerObject = document.getElementsByName(Designer.Data.ParamsContainerName)[0];

		var html = '';

		// Build template and include markers
		html += '<table class="blocks" cellspacing="0" cellpadding="0">';
		html +=   '<thead>';
		html +=     '<tr>';
		html +=       '<th class="shape" id="stickylabel_designer_column_0">&nbsp;</th>';
		html +=       '<th class="type" id="stickylabel_designer_column_1">&nbsp;</th>';
		html +=       '<th class="size" id="stickylabel_designer_column_2">&nbsp;</th>';
		html +=       '<th class="label" id="stickylabel_designer_column_3">&nbsp;</th>';
		html +=       '<th class="print" id="stickylabel_designer_column_4">&nbsp;</th>';
		html +=     '</tr>';
		html +=   '</thead>';
		html +=   '<tbody>';
		html +=     '<tr class="options">';
		html +=       '<td class="shape">' + Designer.Blocks.GetShape() + '</td>';
		html +=       '<td class="type empty">&nbsp;</td>';
		html +=       '<td class="size empty">&nbsp;</td>';
		html +=       '<td class="label empty">&nbsp;</td>';
		html +=       '<td class="print empty">&nbsp;</td>';
		html +=     '</tr>';
		html +=     '<tr class="static-options invisible">';
		html +=       '<td class="shape">&nbsp;</td>';
		html +=       '<td class="type">&nbsp;</td>';
		html +=       '<td class="size">&nbsp;</td>';
		html +=       '<td class="label">&nbsp;</td>';
		html +=       '<td class="print">&nbsp;</td>';
		html +=     '</tr>';
		html +=   '</tbody>';
		html +=   '<tfoot>';
		html +=     '<tr class="preview invisible">';
		html +=       '<td colspan="5"><img src="' + Designer.Base + 'res/images/blank.gif" width="240" height="120" alt="" title="Your labels will look similar to this example at the selected size" /></td>';
		html +=     '</tr>';
		html +=     '<tr class="text">';
		html +=       '<td colspan="5"></td>';
		html +=     '</tr>';

		if (Designer.Options.DynamicPricing) {

			html +=     '<tr class="price invisible">';
			html +=       '<td colspan="5"></td>';
			html +=     '</tr>';
		}

		html +=   '</tfoot>';
		html += '</table>';

		// Output the final HTML code
		hostObj.innerHTML = html;

		// Update instructions
		Designer.Blocks.SetColumnNames(0);

		// Cache footer and footer rows for later use
		var footer = hostObj.getElementsByTagName('tfoot')[0];
		var footerRows = footer.getElementsByTagName('tr');

		// Get a reference to the DOM element that contains the preview image
		Designer.Data.PreviewImageObject = footer.getElementsByTagName('img')[0];

		// Get a reference to the DOM element that contains the text input panel
		Designer.Data.TextPanelObject = footerRows[1];

		if (Designer.Options.DynamicPricing) {

			// Get a reference to the DOM element that contains the price panel
			Designer.Data.PricePanelObject = footerRows[2];

			// Run the ZC interface
			Designer.ZC.Run($('cartAdd'));
		}

		// Restore state from container
		Designer.Restore(Designer.Data.ParamsContainerObject.value);

		return true;
	}
};

// Make sure we have $(...) defined
if (typeof ($) == 'undefined') {

	// Modified version of Prototype's $(...) function
	function $() {
	  var elements = new Array();
	  var element;
	  for (var i = 0; i < arguments.length; i++) {
	    element = arguments[i];
	    if (typeof element == 'string')
	      element = document.getElementById(element);

	    if (arguments.length == 1)
	      return element;

	    elements.push(element);
	  }

	  return elements;
	};
}

if (typeof (htmlescape) == 'undefined') {

	// Modified version of Prototype's escapeHTML(...) function
	function htmlescape(text)
	{
		var divElement = document.createElement('div');
		var textNode = document.createTextNode(text);
		divElement.appendChild(textNode);
		return divElement.innerHTML;
	};
}

if (typeof (trim) == 'undefined') {

	function trim(text) {

		return text.replace(/^\s+|\s+$/g, '');
	};
}
