/**
 * Create an user interface for product customization.
 * 
 * The `.productUiCustomizer()` method creates an user interface for product customization. This includes a product
 * preview, a toolbar and an input mask.
 * 
 * ### Examples
 * Apply .productUiCustomizer() to an unordered list.
 * 
 * JavaScript:
 * 
 *     de_epages('#customizer').productUiCustomizer();
 * 
 * HTML:
 * 
 *     <ul id="customizer"></ul>
 * 
 * 
 * @class jQuery.ui.productUiCustomizer
 * 
 * @uses ep
 * @uses ep.base64
 * @uses ep.dict
 * @uses ep.canvas
 * @uses jQuery.mime
 * @uses jQuery.tmpl
 * @uses jQuery.metaparse
 * @uses jQuery.ui.widget
 * @uses jQuery.ui.draggable
 * @uses jQuery.ui.droppable
 * @uses de_epages.presentation.ui.uploader
 * @since 6.14.0
 */

/**
 * @cfg {Object} data An object containing data for ajax requests (ChangeAction, ChangeObjectID, ViewAction: 'JSONViewResponse').
 */

/**
 * @cfg {Integer} [width] The width of the customizer.
 */

/**
 * @cfg {Integer} [height] The height of the customizer.
 */

/**
 * @cfg {Integer} [maxWidth] The maximal witdh of the displayed product image.
 */

/**
 * @cfg {Integer} [maxHeight] The maximal height of the displayed product image.
 */

/**
 * @cfg {Integer} [prevWidth] The width of the preview image which will be generated.
 */

/**
 * @cfg {Integer} [prevHeight] The height of the preview image which will be generated.
 */

/**
 * @cfg {Object} [minScale] The minimum size an element can be scaled.
 */

/**
 * @cfg {Image} productImg The product image which will be displayed.
 */

/**
 * @cfg {Integer} [zIndex] The minimal z-index for canvas elements.
 */

/**
 * @cfg {Array} areas An array containing the customizable areas.
 */

/**
 * @cfg {String} [accept] Accepted elements for droppable areas.
 */

/**
 * @cfg {Boolean} [showPreview] A boolean that specifies whether the preview will be shown.
 */

/**
 * @cfg {Boolean} [toolbar] A boolean that specifies whether the toolbar will be shown.
 */

/**
 * @cfg {Object} [operations] An object containing possible operations for canvas elements.
 */

/**
 * @cfg {Object} [font] An object containing font settings.
 */

/**
 * @cfg {Object} functions An object containing the toolbar buttons.
 */

/**
 * @cfg {Object} inputData An object containing the data for creating the input elements.
 */

/**
 * @cfg {Object} [form] A form element containing the customizer widget.
 */

/**
 * See `jQuery.ui.productUiCustomizer` for details.
 * 
 * @param {Object} [options] A map of additional options pass to the method.
 * @param {Object} data An object containing data for ajax requests (ChangeAction, ChangeObjectID, ViewAction: 'JSONViewResponse').
 * @param {Integer} [width] The width of the customizer.
 * @param {Integer} [height] The height of the customizer.
 * @param {Integer} [maxWidth] The maximal witdh of the displayed product image.
 * @param {Integer} [maxHeight] The maximal height of the displayed product image.
 * @param {Integer} [prevWidth] The width of the preview image which will be generated.
 * @param {Integer} [prevHeight] The height of the preview image which will be generated.
 * @param {Object} [minScale] The minimum size an element can be scaled.
 * @param {Image} productImg The product image which will be displayed.
 * @param {Integer} [zIndex] The minimal z-index for canvas elements.
 * @param {Array} areas An array containing the customizable areas.
 * @param {String} [accept] Accepted elements for droppable areas.
 * @param {Boolean} [showPreview] A boolean that specifies whether the preview will be shown.
 * @param {Boolean} [toolbar] A boolean that specifies whether the toolbar will be shown.
 * @param {Object} [operations] An object containing possible operations for canvas elements.
 * @param {Object} [font] An object containing font settings.
 * @param {Object} functions An object containing the toolbar buttons.
 * @param {Object} inputData An object containing the data for creating the input elements.
 * @param {Object} [form] A form element containing the customizer widget.
 * 
 * @method productUiCustomizer
 * @member jQuery
 * 
 * @since 6.14.0
 */

/*
 * @copyright		© Copyright 2006-2012, epages GmbH, All Rights Reserved.
 *
 * @module			de_epages/product/ui/customizer
 *
 * @revision		$Revision: 1.16 $
 */

define("de_epages/product/ui/customizer", [
	"jquery",
	"ep",
	"de_epages",
	"underscore",
	"util/base64",
	"util/mime",
	"util/string",
	"$dict!./customizer",
	"$tmpl!./customizer-toolbar",
	"$tmpl!./customizer-inputs",
	"$tmpl!./customizer-preview",

	"jquery/metaparse",
	"jquery/ui/widget",
	"jquery/ui/draggable",
	"jquery/ui/droppable",
	"jquery/ui/sortable",
	"ep/alert",
	"ep/canvas",
	"ep/ui/tooltip",
	"de_epages/presentation/ui/uploader"
], function ($, ep, de_epages, _, base64, mime, str, customizerDict, tmplCustomToolbar, tmplCustomInputs, tmplCustomPreview) {

	/*
	 * @dictionary		ep.dict
	 *
	 * @translation		{OUT_OF_BOUNDS}
	 *								{SupportedFileFormats}
	 *								{MinSize}
	 *								{RemainingCharacters}
	 *								{MandatoryFields}
	 */


	/*
	 * at the end of this file the html structure is added which is created by the customizer widget
	 */

	var tRemainingCharacters = customizerDict.translate('RemainingCharacters'),
		tSupportedFileFormats = customizerDict.translate('SupportedFileFormats'),
		tMinSize = customizerDict.translate('MinSize'),
		tMandatoryFields = customizerDict.translate('MandatoryFields'),

		tExpand = customizerDict.translate('Expand'),
		tDownsize = customizerDict.translate('Downsize'),
		tRotateAntiClockwise = customizerDict.translate('RotateAntiClockwise'),
		tRotateClockwise = customizerDict.translate('RotateClockwise'),
		tMoveToForeground = customizerDict.translate('MoveToForeground'),
		tMoveToBackground = customizerDict.translate('MoveToBackground'),
		tRemove = customizerDict.translate('Remove'),
		tOutOfBounds = customizerDict.translate('OUT_OF_BOUNDS');

	$.widget('ui.productUiCustomizer', {

		//_toolbar:							undefined,
		//_inputMask:						undefined,
		//_productPreview:			undefined,
		//_imageContainer:			undefined,
		//_productImage:				undefined,
		//_areas: 							undefined,
		//_firstArea:						undefined,
		//_selectedCanvas:			undefined,
		//_inputWrapper:				undefined,
		//_base64Image:					undefined,
		//_base64PreviewImage:	undefined,

		//_scaleUpButton:				undefined,
		//_scaleDownButton:			undefined,
		//_rotateLeftButton:		undefined,
		//_rotateRightButton:		undefined,
		//_layerUpButton:				undefined,
		//_layerDownButton:			undefined,
		//_removeButton:				undefined,
		//_hiddenCanvasErrors:	undefined,
		//_selectedInput: 			undefined,
		//_submitButton:				undefined,

		_isOldIE: ($.browser.msie && parseInt($.browser.version, 10) < 9), // browser switch (indicates IEs < 9)

		_areaObject: {}, // contains areas mapped to their position
		_keyupDelay: 300, // time to wait after keyup event (in milliseconds)
		_canvasController: {}, // contains attributes and information for the items (texts, files)
		_errorObject: { // object to handle errors (depending on type and/or function)
			rotate: {},
			file: {}
		},
		tooltip: { // object to handle tooltips (depending on type and/or function)
			//rotate: {},
			//file: {}
		},
		_defaultValues: {
			base: {
				angle: 0,
				top: 0,
				left: 0,
				center: {
					rel_top: 0,
					rel_left: 0,
					abs_top: 0,
					abs_left: 0
				}
			},
			extended: {
				innerWidth: 0,
				innerHeight: 0,
				width: 0,
				height: 0
			}
		},
		_fillFactor: 0.95, //
		_cleanUpGUIDList: [], // list to collect GUIDs who indicates which files have to be deleted when dialog is closed
		_acceptedFiles: ['image/*'], // accepted file formats
		_hTooltip: undefined, // timer handle for tooltip click delay
		_hCKBBTimeoutDelay: 500, // delay after that the continuous exectution of an operation starts
		_hCKBBIntervalDelay: 100, // delay between two operations when continuous calls are established
		_previewMimeTypes: 'image/jpeg,image/png,image/gif',

		_functions: { // default toolbar buttons
			scaleUp: {
				fn: 'scale',
				params: {
					factor: 10
				},
				cssClass: 'scaleUp',
				text: tExpand
			},
			scaleDown: {
				fn: 'scale',
				params: {
					factor: -10
				},
				cssClass: 'scaleDown',
				text: tDownsize
			},
			rotateLeft: {
				fn: 'rotate',
				params: {
					angle: -5
				},
				cssClass: 'rotateLeft',
				text: tRotateAntiClockwise
			},
			rotateRight: {
				fn: 'rotate',
				params: {
					angle: 5
				},
				cssClass: 'rotateRight',
				text: tRotateClockwise
			},
			layerUp: {
				fn: 'layerUpDown',
				params: {
					step: 1
				},
				cssClass: 'layerUp',
				text: tMoveToForeground
			},
			layerDown: {
				fn: 'layerUpDown',
				params: {
					step: -1
				},
				cssClass: 'layerDown',
				text: tMoveToBackground
			},
			remove: {
				fn: 'removeElement',
				cssClass: 'remove',
				text: tRemove
			}
		},

		// css classes used in the widget
		_cssClassCustomizer: 'ep-uiCustomizer',
		_cssClassImageContainer: 'ep-uiCustomizer-imageContainer',
		_cssClassPreview: 'ep-uiCustomizer-preview',
		_cssClassProductImage: 'ep-uiCustomizer-productImage',
		_cssClassSortableArea: 'ep-uiCustomizer-sortableArea',
		_cssClassToolbar: 'ep-uiCustomizer-toolbar',
		_cssClassScaleUp: 'ep-uiCustomizer-scaleUp',
		_cssClassScaleDown: 'ep-uiCustomizer-scaleDown',
		_cssClassRotateLeft: 'ep-uiCustomizer-rotateLeft',
		_cssClassRotateRight: 'ep-uiCustomizer-rotateRight',
		_cssClassLayerUp: 'ep-uiCustomizer-layerUp',
		_cssClassLayerDown: 'ep-uiCustomizer-layerDown',
		_cssClassDisabledButton: 'ep-uiCustomizer-disabledButton',
		_cssClassRemove: 'ep-uiCustomizer-remove',
		_cssClassInputs: 'ep-uiCustomizer-inputs',
		_cssClassInputElements: 'ep-uiCustomizer-inputElements',
		_cssClassInputSelected: 'ep-uiCustomizer-inputSelected',
		_cssClassTextInput: 'ep-uiCustomizer-textInput',
		_cssClassFontFamily: 'ep-uiCustomizer-fontFamily',
		_cssClassFontColor: 'ep-uiCustomizer-fontColor',
		_cssClassSelectedColor: 'selectedColor',
		_cssClassFileInput: 'ep-uiCustomizer-fileInput',
		_cssClassMaxLength: 'ep-uiCustomizer-maxLength',
		_cssClassCanvasSelected: 'ep-uiCustomizer-canvasSelected',
		_cssClassCanvasHover: 'ep-uiCustomizer-canvasHover',
		_cssClassCanvasHoverDraggable: 'ep-uiCustomizer-canvasHoverDraggable',
		_cssClassCanvasError: 'ep-uiCustomizer-canvasError',

		// default options
		options: {
			data: {}, // contains data for ajax requests (ChangeAction, ChangeObjectID, ViewAction: 'JSONViewResponse')
			width: 800, // customizer-width
			height: 600, // customizer-height
			maxWidth: 400, // max. width of product image
			maxHeight: 400, // max. height of product image
			prevWidth: 100, // width of preview image
			prevHeight: 100, // height of preview image
			minScale: { // minimal scale size
				height: 10,
				width: 10
			},
			productImg: undefined, // product image (large)
			zIndex: 1000, // lowest z-index for canvas-elements
			areas: [], // array of containing areas
			accept: 'canvas', // accepted elements for droppable areas
			//contents:				{},						// array of containing content-data
			showPreview: true, // show preview or not
			toolbar: true, // show toolbar or not
			operations: { // possible operations for content-elements
				draggable: true // make contents draggable or not
			},
			font: { // font attributes
				name: 'Arial', // font family
				size: 12, // font size
				resizable: false, // make font resizable or not
				unit: 'pt', // font unit
				color: '#000000' // default font color,
			},
			functions: [], // toolbar buttons
			debug: false, // true = shows a debug window for debugging in older IEs (syntax: this._ownDebug('DebuggingMessage');)
			inputData: undefined, // data for customizerInputs-template
			form: undefined, // if dialog is in an form
			existingLineItemID: '', // contains the LineItemID if any exists
			//dialog: 					{},
			existingBasketID: false // contains the ID of an existing line item if available
		},

		//#JSCOVERAGE_IF false
		// debuggin method (shows a debug window for debuggin in older IEs)
		_ownDebug: function (strOutput) {
			if (!this.options.debug) {
				return;
			}
			this._debugElement[0].innerHTML += new Date() + "<br />" + strOutput + "<br /><br />";
		},
		//#JSCOVERAGE_ENDIF



		_create: function () {
			// init debug element an append to the document if wanted
			if (this.options.debug) {
				this._debugElement = $('<pre />');
				this._debugElement.attr('id', 'debugId')
					.css({
					border: '1px solid #ccc',
					width: '500px',
					height: '200px',
					overflow: 'auto',
					padding: '9px',
					position: 'absolute',
					fontSize: '10px',
					top: '0px',
					right: '0px',
					'z-index': 999999999,
					background: '#efefef'
				});
				$('body')
					.append(this._debugElement);
			}

			var self = this,
				elem = self.element,
				o = self.options;

			// get instance of dict
			self.dict = customizerDict;

			// extract file extensions and generate mime types
			if (o.inputData.fileFormats) {
				self._acceptedFiles = [];
				$.each(o.inputData.fileFormats, function (index, value) {
					self._acceptedFiles.push(mime.mime(value));
				});
			}

			// set default operations if necessary
			o.functions = _.pick(self._functions, o.functions);

			// set css styles for the customizer element
			elem.addClass(self._cssClassCustomizer)
				.css({
				width: o.width + 'px',
				height: o.height + 'px'
			});

			// insert hidden input to handle errors for canvas elements (do not submit form if formInvalid property is true)
			self._hiddenCanvasErrors = $('<input type="hidden" name="epCustomizerCanvasErrors" />');
			elem.parent()
				.append(self._hiddenCanvasErrors);

			// add template for product image
			if (o.productImg || o.showPreview) {
				tmplCustomPreview({
					areas: o.areas,
					image: o.productImg
				})
				.appendTo(elem);

				self._productPreview = $('.' + self._cssClassPreview);
				self._imageContainer = self._productPreview
					.find('.' + self._cssClassImageContainer)
					.css({
						width: o.maxWidth + 'px',
						height: o.maxHeight + 'px'
					});
			}


			// loop over areas and set them droppable
			if (o.showPreview) {
				self._areas = self._imageContainer.children('div[data-type=area]');
				self._areas.droppable({
					accept: o.accept,
					tolerance: 'fit',
					drop: function (event, ui) {
						self._onDrop.call(this, event, ui, self);
					}
				});

				// make area aliases accessable by using the position
				$.each(o.areas, function (index, entry) {
					self._areaObject[entry.areaNo] = entry;
				});

			}

			// set firstArea property, because all canvas elements will be inserted in the first area when being created AND should be previewed
			self._firstArea = (o.showPreview) ? $(self._areas[0]) : 0;

			// load product image and center it in the image container
			self._productImage = $('<img />')
				.prependTo(self._imageContainer);
			self._productImage.on('load', function () {
				var dimension = self._recalcDimensions(this, {
						maxWidth: o.maxWidth,
						maxHeight: o.maxHeight
					});
				$(this)
					.css({
					left: (o.maxWidth / 2 - dimension.w / 2) + 'px',
					top: (o.maxHeight / 2 - dimension.h / 2) + 'px',
					width: dimension.w + 'px',
					height: dimension.h + 'px',
					visibility: 'visible'
				});
			});

			self._productImage.attr('src', o.productImg + (self._isOldIE ? ("?" + new Date()
				.getTime()) : ''))
				.addClass(self._cssClassProductImage)
				.css({
				visibility: 'hidden'
			});

			// add template for toolbar
			if (o.toolbar && o.showPreview) {
				tmplCustomToolbar(o)
					.appendTo(self._productPreview);

				self._toolbar = $('.' + self._cssClassToolbar, elem);

				self._toolbar.children('li')
					.each(function () {
						// get additional parameters if available
						var params = (o.functions[$(this)
								.data('key')].params) || {};
						// execute mousedown handler with an object as this-reference
						$(this)
							.on('mousedown', function () {
								self[$(this)
									.data('method')]({
									params: params,
									method: $(this)
										.data('method')
								});
							});
						// clear all coordinating timer handler when mouse button is released
						$(this)
							.on('mouseup', $.proxy(self._CKBBSpecial, {
							self: self
						}));
					});
			}

			var translationsInputMask = {
					'tRemainingCharacters': tRemainingCharacters,
					'tMinSize': tMinSize,
					'tSupportedFileFormats': tSupportedFileFormats,
					'tMandatoryFields': tMandatoryFields
				},
				helperFunction = {
					i: 0,
					counter: function () {
						this.i++;
						return "";
					}
				};

			// add templat for input fields
			self.testTmpl = tmplCustomInputs($.extend(o.inputData, translationsInputMask), helperFunction); //.appendTo(elem);

			self.testTmpl.appendTo(elem);
			self._inputMask = $('.' + self._cssClassInputs, elem);
			self._initInputElements();

			// wait a moment so that input elements can be rendered
			setTimeout(function () {
				// force browser to re-render
				self.testTmpl.width(self.testTmpl.width());
			}, 100);

			// add wrapper around input section to handle different states (e.g. hover, focus, active ...)
			self._inputWrapper = elem.find('.' + self._cssClassInputElements);
			self._inputWrapper.on('click', function () {
				var inputAlias = $(this)
						.data('alias');
				self._handleCanvasSelection(inputAlias);
				self._handleInputSelection(inputAlias);
			})
				.data({
				fontFamily: o.font.name,
				fontColor: o.font.color,
				fontSize: o.font.size,
				fontUnit: o.font.unit
			});

			if (o.dialog) {
				var $dialog = $(o.dialog),
					$form = $dialog.uiDialog('option', 'form');

				$form.on('submit', function () {
					$dialog.closest('.ui-dialog')
						.busy();
					$('.ep-busy')
						.css({
						zIndex: 9999999
					});
				})
					.on('submitFail', function () {
						$dialog.closest('.ui-dialog')
							.busy('hide');
					});

				self._submitButton = $dialog.find('.epCustomizerAddToBasket');

				self._submitButton.on('click', function (event) {
					// CleanUpTempCustomizedFiles
					var CleanupGUIDList = $('<input type="hidden" name="CleanupGUIDList" />')
							.appendTo(o.dialog),
						JSON = self.generateCustomizedJSON();

					CleanupGUIDList.val(base64.encode(self.getGUIDList()));
					// insert Base64 image data string in hidden input
					$dialog.find('input[name=Base64Image]')
						.val(self.getBase64Image());
					$dialog.find('input[name=Base64PreviewImage]')
						.val(self.getBase64PreviewImage());

					// if just the ExistingLineItemId is handed over -> create hidden input and reset JSON
					if (typeof JSON == "number") {
						var existingLineItemId = $('<input type="hidden" name="ExistingLineItemID" />')
								.appendTo(o.dialog);
						existingLineItemId.val(JSON);
						JSON = {};
					}

					// insert Base64 options string in hidden input
					$dialog.find('input[name=Base64CustomizedAreas]')
						.val(base64.encode(JSON === "" ? {} : JSON));


					// remove hidden input for canvas error handling
					$dialog.find('input[name=epCustomizerCanvasErrors]')
						.remove();

					if (o.existingBasketID) {
						$dialog.find('input[name=ViewObjectID]')
							.val(o.existingBasketID);
					}
					//event.preventDefault();
				});

				// change dialog size to fix rendering problems in IE9
				if ($.browser.msie && parseInt($.browser.version, 10) === 9) {
					setTimeout(function () {
						$dialog.uiDialog('option', 'width', $dialog.width() + 1);
					}, 0);
				}
			}

			// if data has/have to be restored
			if (o.contents !== undefined) {
				self._restoreCustomizedData();
			}
		},


		// method to restore previous customizer settings
		_restoreCustomizedData: function () {
			var self = this,
				elem = self.element,
				o = self.options,
				inputAlias;

			// loop over contents and copy data into canvasController
			for (var key in o.contents) {
				o.existingLineItemID = o.contents[key].ExistingLineItemID;
				inputAlias = "_" + key;

				// fill controller object
				var controllerObject = {
						type: o.contents[key].Type.toLowerCase(),
						ExistingLineItemID: o.contents[key].ExistingLineItemID,
						height: o.contents[key].Height,
						width: o.contents[key].Width,
						innerHeight: o.contents[key].InnerHeight,
						innerWidth: o.contents[key].InnerWidth,
						top: o.contents[key].FromTop,
						left: o.contents[key].FromLeft,
						zIndex: o.contents[key].ZIndex,
						angle: o.contents[key].Orientation,
						area: o.contents[key].AreaID,
						center: {
							rel_top: o.contents[key].Height / 2,
							rel_left: o.contents[key].Width / 2,
							abs_top: parseInt(o.contents[key].FromTop) + parseInt(o.contents[key].Height) / 2,
							abs_left: parseInt(o.contents[key].FromLeft) + parseInt(o.contents[key].Width) / 2
						}
					};

				// fill controller object with special values for text elements
				if (o.contents[key].Type.toLowerCase() === "text") {
					$.extend(controllerObject, {
						fontSize: o.contents[key].FontSize,
						fontColor: o.contents[key].Color,
						fontFamily: o.contents[key].FontName,
						text: o.contents[key].Text,
						fontUnit: o.font.unit,
						preview: true
					});

					self._inputMask.find('.' + self._cssClassFontColor + '[data-alias=_' + key + '] li')
						.removeClass(self._cssClassSelectedColor);
					self._inputMask.find('.' + self._cssClassFontColor + '[data-alias=_' + key + '] li[data-color=' + o.contents[key].Color + ']')
						.addClass(self._cssClassSelectedColor);
					// fill controller object with special values for file elements
				}
				else if (o.contents[key].Type.toLowerCase() === "file") {
					$.extend(controllerObject, {
						FileName: o.contents[key].FileName,
						FilePath: o.webPath + o.contents[key].FilePath.substring(0, o.contents[key].FilePath.lastIndexOf("/") + 1),
						src: o.webPath + o.contents[key].FilePath,
						GUID: o.contents[key].GUID,
						preview: true
					});
					// if file has an mime type which should be shown
					if (!mime.mime(o.contents[key].FileName.substr(o.contents[key].FileName.lastIndexOf(".") + 1), self._previewMimeTypes)) {
						$.extend(controllerObject, {
							recalc: true,
							preview: false
						});
					}
				}

				// update canvas controller with restoe data
				self._updateCanvasController(inputAlias, controllerObject);

				if (o.contents[key].Type.toLowerCase() === "text") {
					// insert canvas
					self._insertCanvas(inputAlias, {
						area: o.contents[key].AreaID
					});

					var selfInput = self.element.find('.' + self._cssClassTextInput + '[data-alias=' + inputAlias + ']'),
						selfSelected = self._inputMask.find('select[data-alias=' + inputAlias + ']:first');

					// if a select element depends to the text element -> restore selection
					if (selfSelected) {
						selfSelected.children('option')
							.each(function () {
								var $this = $(this);

								if ($this.val() == o.contents[key].FontName) {
									// select restore font family option in select element
									$this.attr('selected', true);
								}
							});
					}

					if (o.showPreview) {
						self._setTopLeft(inputAlias);
						self._updateCanvas(inputAlias, {
							resizeCanvas: true
						});
					}
					else {
						selfInput.val(o.contents[key].Text);
					}

					// trigger keyup event to change the number of remaining characters
					selfInput.trigger('keyup');
				}
				else if (o.contents[key].Type.toLowerCase() === "file") {
					self._insertImageToCanvas(inputAlias);
				}

			}

			// store LineItemID without leading underscore to options
			o.existingLineItemID = o.contents[inputAlias.substring(1)].ExistingLineItemID;
		},


		// method to position canvas element
		_setTopLeft: function (inputAlias) {
			var self = this,
				elem = self.element,
				o = self.options,
				$canvas = self._imageContainer.find('canvas.' + inputAlias),
				canvasCtrl = self._canvasController[inputAlias];

			$canvas.css({
				top: canvasCtrl.top + 'px',
				left: canvasCtrl.left + 'px'
			});
		},


		// initializes input elements for texts and files
		_initInputElements: function () {
			var self = this,
				elem = self.element,
				o = self.options;

			// iterate over all possible input container (containing textareas, inputs, file inputs)
			self._inputMask.children('.' + self._cssClassInputElements)
				.each(function (index) {
					var $this = $(this),
						textInputs = $this.find('.' + self._cssClassTextInput),
						fontColors = $this.find('.' + self._cssClassFontColor),
						fontFamily = $this.find('.' + self._cssClassFontFamily),
						fileInputs = $this.find('.' + self._cssClassFileInput);

					// if container contains text input elements
					if (textInputs.length) {
						/*textInputs.on('focus', function(event){
							self._handleCanvasSelection($(this).data('alias'));
              			})*/
						textInputs
							.metaparse()
							.on('keydown', function (event) {
								// prevent submit if ENTER is pressed in a single line text field
								if ((this.tagName.toLowerCase() == "input") && (event.keyCode === $.ui.keyCode.ENTER)) {
									event.preventDefault();
								}
							})
							.on('keyup', function (event) {
								// if a new keyup event fires before the keyup delay is expired
								if (self._hTimer !== undefined) {
									// clear current delay and set to undefined
									clearTimeout(self._hTimer);
									self._hTimer = undefined;
								}

								var $input = $(this), // current input element
									inputAlias = $input.data('alias');

								// set number of remaining characters
								self._changeMaxLength($input);

								// wait a moment after keyup
								self._hTimer = setTimeout(function () {
									// if no canvas element exists which is linked to the current input element
									if (elem.find('canvas.' + inputAlias)
										.length == 0) {
										// insert new canvas element
										self._insertCanvas(inputAlias, {
											type: 'text',
											area: ((o.showPreview) ? self._firstArea.data('position') : 0)
										});
									}

									// write current input value to canvas controller
									self._canvasController[inputAlias].text = $input.val();

									if (o.showPreview) {
										// if input is empty -> remove canvas
										if ($input.val() === "") {
											self.removeElement();
										}
										else {
											// update canvas element with new content
											self._updateCanvas(inputAlias);
											// set number of remaining characters
											self._changeMaxLength($input);
										}
									}
								}, self._keyupDelay);
							});

						// handle font color fields
						fontColors.children('li')
							.each(function (i) {
								var $li = $(this);
								$li.on('click', function () {
									self._onColorClick($li);
								});
							});

						// handle font family select group
						fontFamily.on('change', function () {
							self._onFontChange($(this));
						});

					}
					else if (fileInputs.length) {
						fileInputs.each(function (i) {
							var $input = $(this),
								inputAlias = $input.data('alias');

							// if file is required -> set flag in error object
							if ($input.data('required')) {
								self._setValidState({
									element: $input,
									invalid: true,
									type: 'file',
									inputAlias: inputAlias
								});
							}

							$(this)
								.presentationUiUploader({
									accept: self._acceptedFiles.join(","),
									multiple: false,
									data: $.extend(o.data, {
										FileOptionID: parseInt(inputAlias.substr(1))
									}),
									callback: function (data, statusMsg) {
										self._uploaderCallback(data, statusMsg, inputAlias, $input);
									}
								});
						});
					}

				});
		},

		// change the number of remaining characters for text inputs
		_changeMaxLength: function (input) {
			var self = this,
				elem = self.element,
				o = self.options,
				maxLengthSpan = input.closest('div.' + self._cssClassInputElements)
					.find('.' + self._cssClassMaxLength);

			if (input.data('length') - input.val()
				.length < 0) {
				maxLengthSpan.html('0');
				input.val(input.val()
					.substring(0, input.data('length')));
			}
			else {
				maxLengthSpan.html(input.data('length') - input.val()
					.length);
			}
		},


		// method to handle response data after upload
		_uploaderCallback: function (data, statusMsg, inputAlias, $input) {
			var self = this,
				elem = self.element,
				o = self.options;

			if (statusMsg === "success") {
				var success = data[0].success;

				// update canvas controller with new file data
				self._updateCanvasController(inputAlias, {
					type: 'file',
					//src: success.uploadedFile,
					GUID: success.GUID,
					FileName: success.FileName,
					FilePath: success.uploadedFile.substring(0, success.uploadedFile.lastIndexOf("/") + 1),
					area: (self._canvasController[inputAlias] !== undefined && self._canvasController[inputAlias].src !== undefined) ? self._canvasController[inputAlias] : ((self.options.showPreview) ? self._firstArea.data('position') : 0)
				});

				self._insertImageToCanvas(inputAlias);
			}

		},


		// insert a new canvas element associated to the initiating input element
		_insertCanvas: function (inputAlias, settings, event) {
			var self = this,
				elem = self.element,
				o = self.options,
				areaElement = $('#customizerArea_' + settings.area),
				wrapper = $('#customizerInput_' + inputAlias),
				canvasCtrl = self._canvasController[inputAlias],
				zIndex = ((canvasCtrl !== undefined && canvasCtrl.zIndex !== undefined) ? canvasCtrl.zIndex : o.zIndex + areaElement.children('canvas')
					.length + 1);

			if (o.showPreview) {
				$('<canvas />')
					.appendTo(areaElement);
				var $canvas = areaElement.children('canvas:last');

				$canvas.addClass(inputAlias)
					.data('alias', inputAlias)
					.css({
					backgroundImage: 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QUUxRjNGMzI2OEYyMTFFMUE0RDJGQjIwQjcxNDY4NDgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QUUxRjNGMzM2OEYyMTFFMUE0RDJGQjIwQjcxNDY4NDgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpBRTFGM0YzMDY4RjIxMUUxQTREMkZCMjBCNzE0Njg0OCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpBRTFGM0YzMTY4RjIxMUUxQTREMkZCMjBCNzE0Njg0OCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PiCPaZ0AAAAQSURBVHjaYvj//z8DQIABAAj8Av7bok0WAAAAAElFTkSuQmCC)',
					position: 'absolute',
					zIndex: zIndex //o.zIndex + areaElement.children('canvas').length
				})
					.on('mousedown', function () {
						self._handleCanvasSelection(inputAlias);

						// set focus on selected text input
						if (canvasCtrl !== undefined && canvasCtrl.type === "text") {
							$('#customizerInput_' + inputAlias)
								.find('.' + self._cssClassTextInput)
								.focus();
						}
					})
					.on('mouseenter', function () {
						$(this)
							.addClass(self._cssClassCanvasHover);
						if (o.operations.draggable) {
							$(this)
								.addClass(self._cssClassCanvasHoverDraggable);
						}
					})
					.on('mouseleave', function () {
						$(this)
							.removeClass(self._cssClassCanvasHover);
					});

				// if wanted -> make canvas draggable
				if (o.operations.draggable) {
					$canvas.draggable({
						containment: self._imageContainer,
						revert: 'invalid',
						revertDuration: 300
					});
				}

				self._handleCanvasSelection(inputAlias);
			}

			var controllerOptions = {};
			if (canvasCtrl === undefined || (canvasCtrl !== undefined && canvasCtrl.ExistingLineItemID === undefined)) {
				controllerOptions = {
					zIndex: o.zIndex + ((o.showPreview) ? areaElement.children('canvas')
						.length : 0),
					type: settings.type,
					area: ((o.showPreview) ? settings.area : 0),
					alias: inputAlias,
					preview: true
				};
				$.extend(controllerOptions, self._defaultValues.base);

				if (settings.type === 'text') {
					$.extend(controllerOptions, {
						fontSize: ((canvasCtrl !== undefined && canvasCtrl.fontSize !== undefined) ? canvasCtrl.fontSize : wrapper.data('fontSize')),
						fontUnit: ((canvasCtrl !== undefined && canvasCtrl.fontUnit !== undefined) ? canvasCtrl.fontUnit : wrapper.data('fontUnit')),
						fontFamily: ((canvasCtrl !== undefined && canvasCtrl.fontFamily !== undefined) ? canvasCtrl.fontFamily : wrapper.data('fontFamily')),
						fontColor: ((canvasCtrl !== undefined && canvasCtrl.fontColor !== undefined) ? canvasCtrl.fontColor : wrapper.data('fontColor')),
						first: true
					});

					if (!o.showPreview) {
						$.extend(controllerOptions, self._defaultValues.extended);
					}
				}
			}

			// create entry in canvas controller for new element
			self._updateCanvasController(inputAlias, controllerOptions);
		},


		// insert an image to the according canvas element
		_insertImageToCanvas: function (inputAlias) {
			var self = this,
				elem = self.element,
				o = self.options,
				canvasCtrl = self._canvasController[inputAlias],
				areaPosition = canvasCtrl.area,
				recalc = true,
				fileName = canvasCtrl.FileName;

			// save GUIDs for remove on server side
			self._cleanUpGUIDList.push(canvasCtrl.GUID);
			// fill html element with new file name
			if (fileName.length > 50) {
				fileName = str.shrink(fileName, {
					length: 50,
					ratio: 0.5,
					spacer: "..."
				});
			}
			self._inputMask.find(".ep-uiCustomizer-fileName[data-alias=" + inputAlias + "]")
				.html(fileName);

			// if file is not allowed for preview
			if (!mime.mime(canvasCtrl.FileName.substr(canvasCtrl.FileName.lastIndexOf(".") + 1), self._previewMimeTypes)) {

				// if already a corresponding canvas element exists -> remove and set defaults to canvasController
				if (self._imageContainer.find('canvas.' + inputAlias)
					.length !== 0) {
					self._removeCanvas();
				}
				else {
					var areaElement = $('#customizerArea_' + canvasCtrl.area);

					// set default values to canvas controller
					self._updateCanvasController(
					inputAlias,
					$.extend({
						zIndex: ((canvasCtrl !== undefined && canvasCtrl.zIndex !== undefined) ? canvasCtrl.zIndex : o.zIndex + areaElement.children('canvas')
							.length + 1),
						area: areaPosition,
						preview: false
					},
					self._defaultValues.base,
					self._defaultValues.extended));
				}

				return;
			}

			// new object for uploaded image
			var newImage = $('<img />')
					.appendTo(self.element.parent()),
				imgSrc;

			// if already an file entry for this alias exists
			if (canvasCtrl !== undefined && canvasCtrl.src !== undefined && canvasCtrl.recalc === undefined) {
				self._handleCanvasSelection(inputAlias);
				areaPosition = canvasCtrl.area;
				recalc = false;
			}

			// if already an image exists in the DOM/canvas controller
			if (canvasCtrl !== undefined && canvasCtrl.image !== undefined) {
				canvasCtrl.image.remove();
				delete canvasCtrl.image;
			}

			if (canvasCtrl.recalc) {
				delete canvasCtrl.recalc;
			}

			canvasCtrl.src = canvasCtrl.FilePath + canvasCtrl.FileName;
			imgSrc = canvasCtrl.src;

			newImage.on('load', function () {
				var _this = this,
					$image = $(_this);

				// if no canvas element exists which is linked to the current input element
				if (elem.find('canvas.' + inputAlias)
					.length == 0) {
					// insert new canvas element
					self._insertCanvas(inputAlias, {
						type: 'file',
						area: ((self.options.showPreview) ? areaPosition : 0)
					});

					// write reference to new image object to canvas controller
					self._updateCanvasController(inputAlias, {
						image: newImage
					});
					// call scale method (opera hack)
					self["_scale"].call({
						self: self,
						params: {
							factor: 1
						},
						method: "_scale"
					});
				}

				if (self.options.showPreview) {
					var area = self._imageContainer.find('[data-position=' + canvasCtrl.area + ']'),
						areaWidth = area.width(),
						areaHeight = area.height(),
						recalcDimensions,
						dimensions,
						updateObject;

					// if image dimensions have to be calculated
					if (recalc) {
						recalcDimensions = {
							maxWidth: areaWidth * self._fillFactor,
							maxHeight: areaHeight * self._fillFactor
						};

						dimensions = self._recalcDimensions(_this, recalcDimensions);

						updateObject = {
							top: (areaHeight - dimensions.h) / 2,
							left: (areaWidth - dimensions.w) / 2,
							center: {
								rel_top: dimensions.h / 2,
								rel_left: dimensions.w / 2,
								abs_top: areaHeight / 2,
								abs_left: areaWidth / 2
							}
						};
					}
					else {
						recalcDimensions = {
							maxWidth: canvasCtrl.innerWidth,
							maxHeight: canvasCtrl.innerHeight,
							longer: true
						};

						dimensions = self._recalcDimensions(_this, recalcDimensions);

						updateObject = {
							src: imgSrc,
							GUID: canvasCtrl.GUID,
							FileName: canvasCtrl.FileName,
							FilePath: canvasCtrl.FilePath
						};

						// if one of the new dimensions don't fit into the area -> recalculate again
						if (dimensions.w > areaWidth || dimensions.h > areaHeight) {
							dimensions = self._recalcDimensions(_this, {
								maxWidth: canvasCtrl.innerWidth,
								maxHeight: canvasCtrl.innerHeight
							});
						}
					}

					self._updateCanvasController(inputAlias, $.extend(updateObject, {
						baseWidth: $image.width(),
						baseHeight: $image.height(),
						innerWidth: dimensions.w,
						innerHeight: dimensions.h,
						width: dimensions.w,
						height: dimensions.h,
						image: newImage
					}));

					self._setTopLeft(inputAlias);

					// update canvas element with new content
					//self._updateCanvas(inputAlias);
					self._rotate.call({
						self: self,
						params: {
							angle: 0
						},
						method: 'rotate'
					});

				}
				else {
					self._updateCanvasController(inputAlias, {
						baseWidth: $image.width(),
						baseHeight: $image.height(),
						innerWidth: $image.width(),
						innerHeight: $image.width(),
						width: $image.width(),
						height: $image.width()
					});

				}

				// update valid state for element
				self._setValidState({
					element: self._inputMask.find('input[data-alias=' + inputAlias + ']'),
					invalid: false,
					type: 'file',
					inputAlias: inputAlias
				});

				//newImage.remove();
			});

			newImage
			// remove comment in next line if IEs < 9 don't display the image correctly
			.attr('src', imgSrc) // + (self._isOldIE ? ("?"+new Date().getTime()) : ''))
			.css({
				visibility: 'hidden',
				position: 'absolute',
				zIndex: 99999,
				top: '-99999px',
				left: '-99999px'
			});
		},


		// render new content to canvas
		_updateCanvas: function (inputAlias, opts) {
			var self = this,
				o = self.options,
				elem = self.element,
				$canvas = self._imageContainer.find('canvas.' + inputAlias),
				canvasCtrl = self._canvasController[inputAlias],
				$input = elem.find('.' + self._cssClassTextInput + '[data-alias=' + inputAlias + ']'),
				opts = opts || {},
				area = $('#customizerArea_' + canvasCtrl.area),
				areaWidth = area.width(),
				areaHeight = area.height();

			if (canvasCtrl.type === 'text') {
				// calculate necessary values for multiline text
				var newTextDimensions = $canvas.canvas()
						.calcTextDimensions({
						phi: canvasCtrl.angle,
						text: canvasCtrl.text,
						fontSize: canvasCtrl.fontSize,
						fontFamily: canvasCtrl.fontFamily,
						fontUnit: o.font.unit,
						resizable: (opts.resizable !== undefined && opts.resizable) ? false : o.font.resizable, // true => text will be trimmed so that it fits to canvas
						delimiter: "\n",
						minSize: o.minScale.height,
						maxWidth: ((opts.resizable !== undefined) ? undefined : (areaWidth - canvasCtrl.left)),
						maxHeight: ((opts.resizable !== undefined) ? undefined : (areaHeight - canvasCtrl.top)),
						resizeCanvas: ((opts.resizeCanvas !== undefined) ? opts.resizeCanvas : true) // true => font size gets scaled so that it fits to canvas
					}),
					content = $input.val(),
					top = (canvasCtrl.first) ? (areaHeight - newTextDimensions.h) / 2 : canvasCtrl.top,
					left = (canvasCtrl.first) ? (areaWidth - newTextDimensions.w) / 2 : canvasCtrl.left;

				// insert text to input if it was modified
				if (content != newTextDimensions.text) {
					$input.val(newTextDimensions.text);
				}

				// update canvas controller with new values
				self._updateCanvasController(inputAlias, {
					fontSize: newTextDimensions.fontSize, //newTextDimensions.scale*canvasCtrl.fontSize,
					fontFamily: canvasCtrl.fontFamily,
					width: newTextDimensions.w,
					height: newTextDimensions.h,
					innerWidth: newTextDimensions.innerW,
					innerHeight: newTextDimensions.innerH,
					text: newTextDimensions.text,
					top: top,
					left: left,
					center: {
						rel_top: newTextDimensions.h / 2,
						rel_left: newTextDimensions.w / 2,
						abs_top: top + newTextDimensions.h / 2,
						abs_left: left + newTextDimensions.w / 2
					},
					lines: newTextDimensions.multiLines.length
				});

				// set new font size
				$('#customizerInput_' + inputAlias)
					.data('fontSize', canvasCtrl.fontSize);

				// delete flag that canvas controller entry is used first time
				delete canvasCtrl.first;

				// redimension canvas element, set format and render multiline text
				$canvas.attr({
					width: canvasCtrl.width,
					height: canvasCtrl.height
				})
					.canvas()
					.clearRect(0, 0, canvasCtrl.width, canvasCtrl.width)
					.fillStyle(canvasCtrl.fontColor)
					.multiline({
					x: newTextDimensions.x,
					y: newTextDimensions.y,
					phi: canvasCtrl.angle,
					text: canvasCtrl.text,
					fontSize: canvasCtrl.fontSize,
					fontFamily: canvasCtrl.fontFamily,
					fontUnit: o.font.unit,
					align: 'left',
					delimiter: "\n"
				});

				// if an error occurs
				if ((canvasCtrl.height + canvasCtrl.top > self._areaObject[canvasCtrl.area].height) || (canvasCtrl.width + canvasCtrl.left > self._areaObject[canvasCtrl.area].width)) {
					self._setValidState({
						element: self._selectedCanvas,
						invalid: true,
						type: 'rotate',
						inputAlias: inputAlias
					});
				}

				self._setTopLeft(inputAlias);

			}
			else if (canvasCtrl.type === 'file') {
				//				var newImage = new Image();
				//				newImage.src = canvasCtrl.src;

				$canvas.attr({
					width: canvasCtrl.width,
					height: canvasCtrl.height
				});

				// translate, rotate and draw image
				$canvas.canvas()
					.save()
					.translate(canvasCtrl.width / 2, canvasCtrl.height / 2)
					.rotate(canvasCtrl.angle * Math.PI / 180)
					.translate(-(canvasCtrl.width / 2), - (canvasCtrl.height / 2))
					.drawImage(canvasCtrl.image[0], 0.5 * (canvasCtrl.width - canvasCtrl.innerWidth), 0.5 * (canvasCtrl.height - canvasCtrl.innerHeight), canvasCtrl.innerWidth, canvasCtrl.innerHeight)
					.restore();

				// update canvas controller with new values
				self._updateCanvasController(inputAlias, {
					center: {
						rel_top: (canvasCtrl.height / 2),
						rel_left: (canvasCtrl.width / 2),
						abs_top: canvasCtrl.top + canvasCtrl.height / 2,
						abs_left: canvasCtrl.left + canvasCtrl.width / 2
					},
					preview: true
				});

			}
		},


		// marks input as selected
		_handleInputSelection: function (inputAlias) {
			this._selectedInput = inputAlias;
		},


		// method called when a canvas is selected
		_handleCanvasSelection: function (inputAlias) {
			var self = this,
				elem = self.element,
				o = self.options,
				canvasCtrl = self._canvasController[inputAlias],
				$input = $('#customizerInput_' + inputAlias)
					.find('.' + self._cssClassTextInput);

			if (self._selectedCanvas !== undefined) {
				self._selectedCanvas.removeClass(self._cssClassCanvasSelected);
			}

			self._inputWrapper.removeClass(self._cssClassInputSelected);

			if (self.options.showPreview) {
				self._selectedCanvas = self._imageContainer.find('canvas.' + inputAlias);
				self._selectedCanvas.addClass(self._cssClassCanvasSelected);
			}
			$('#customizerInput_' + inputAlias)
				.addClass(self._cssClassInputSelected);

			// disable scale buttons if texts are not resizable
			if (o.productImg && o.areas.length) {
				if (!o.font.resizable && $input.length) {
					self._toolbar.find('.' + self._cssClassScaleUp + ', .' + self._cssClassScaleDown)
						.addClass(self._cssClassDisabledButton);
				}
				else {
					self._toolbar.find('.' + self._cssClassScaleUp + ', .' + self._cssClassScaleDown)
						.removeClass(self._cssClassDisabledButton);
				}
			}

		},


		// updates the canvas controller for an entry
		_updateCanvasController: function (key, options) {
			var self = this,
				elem = self.element,
				o = self.options;

			// ensure that an entry exists
			self._canvasController[key] = self._canvasController[key] || {};
			// write comitted values to canvas controller
			$.extend(self._canvasController[key], options);

			// set integer values for top and left offset in area
			self._canvasController[key].top = Math.round(self._canvasController[key].top);
			self._canvasController[key].left = Math.round(self._canvasController[key].left);
		},


		// organize zIndices so that there is no 'numeric space'
		_reorganizeZIndex: function ($area, zIndexRemoved) {
			var self = this,
				elem = self.element,
				o = self.options;

			// get all canvas elements in an area
			$area.children('canvas')
				.each(function () {
					var $thisCanvas = $(this),
						zIndex = parseInt($thisCanvas.css('z-index'));

					// decrement all zIndices greater than the removed one
					if (zIndex > zIndexRemoved) {
						$thisCanvas.css('z-index', zIndex - 1);
						self._updateCanvasController($thisCanvas.data('alias'), {
							zIndex: zIndex - 1
						});
					}
				});
		},


		// callback - called after a canvas was dropped
		_onDrop: function (event, ui, self) {
			var elem = self.element,
				o = self.options,
				$area = $(this),
				inputAlias = ui.draggable.data('alias'),
				parent = ui.draggable.parent(),
				zIndexDraggable = parseInt(ui.draggable.css('z-index')),
				parentPosition = parent.position(),
				targetPosition = $area.position(),
				newLeft = parentPosition.left + parseInt(ui.draggable.css('left')) - targetPosition.left,
				newTop = parentPosition.top + parseInt(ui.draggable.css('top')) - targetPosition.top,
				innerHTML;

			// if target is not equal parent
			if (ui.draggable.parent()[0] != this) {
				// append canvas to target
				$area.append(ui.draggable);
				// re-position canvas in target area
				ui.draggable.css({
					left: newLeft + 'px',
					top: newTop + 'px',
					zIndex: o.zIndex + $area.children('canvas')
						.length
				});

				self._reorganizeZIndex(parent, zIndexDraggable);

				self._updateCanvasController(inputAlias, {
					zIndex: o.zIndex + $area.children('canvas')
						.length
				});

				// workaround for IE < 9
				//if(!ui.draggable.is(':empty')){
				if (self._isOldIE) {
					self._updateCanvas(inputAlias);
				}

			}

			// set valid state for drag element
			$.each(['rotate'], function (index, value) {
				self._setValidState({
					element: ui.draggable,
					invalid: false,
					type: value,
					inputAlias: ui.draggable.data('alias')
				});
			});

			// update canvas controller with new values
			self._updateCanvasController(inputAlias, {
				area: $area.data('position'),
				top: newTop,
				left: newLeft,
				center: {
					rel_top: self._canvasController[inputAlias].center.rel_top,
					rel_left: self._canvasController[inputAlias].center.rel_left,
					abs_top: newTop + self._canvasController[inputAlias].center.rel_top,
					abs_left: newLeft + self._canvasController[inputAlias].center.rel_left
				}
			});
		},


		// handles click and sets new color
		_onColorClick: function ($li) {
			var self = this,
				elem = self.element,
				o = self.options,
				inputAlias = $li.closest('ul')
					.data('alias');

			$li.siblings('li')
				.removeClass(self._cssClassSelectedColor)
				.data('selected', 0);
			$li.addClass(self._cssClassSelectedColor)
				.data('selected', 1);

			self._updateCanvasController(inputAlias, {
				fontColor: $li.data('color')
			});

			$('#customizerInput_' + inputAlias)
				.data('fontColor', $li.data('color'));

			if (self.options.showPreview) {
				self._updateCanvas(inputAlias);
			}
		},


		// handles change event for select box and sets new font family
		_onFontChange: function ($selectbox) {
			var self = this,
				elem = self.element,
				o = self.options,
				inputAlias = $selectbox.data('alias');

			self._updateCanvasController(inputAlias, {
				fontFamily: $selectbox.val()
			});

			/*self.element.find('.'+self._cssClassTextInput+'[data-alias=' + inputAlias + ']').css({
        fontFamily: $selectbox.val()
      });*/

			$('#customizerInput_' + inputAlias)
				.data('fontFamily', $selectbox.val());

			if (self.options.showPreview) {
				self._updateCanvas(inputAlias, {
					resizeCanvas: false
				});
			}

		},


		// clears timer handles which cooridnating the continuous execution of operation
		_CKBBSpecial: function () {
			var self = this.self,
				elem = self.element,
				o = self.options;

			if (self._hCKBBTimeout) {
				clearInterval(self._hCKBBTimeout);
				self._hCKBBTimeout = undefined;
			}

			if (self._hCKBBInterval) {
				clearInterval(self._hCKBBInterval);
				self._hCKBBInterval = undefined;
			}
		},


		// hander which coordinates the continuous execution of operations
		_continueHandler: function (options) {
			var self = options.self,
				elem = self.element,
				o = self.options,
				params = options.params,
				method = options.method;

			self["_" + method].call({
				self: self,
				params: params,
				method: "_" + method
			});

			// delay for next event
			self._hCKBBTimeout = setTimeout(function () {
				// delay for keyup
				self._hCKBBInterval = setInterval(function () {
					if (self._hCKBBInterval !== undefined) {
						self["_" + method].call({
							self: self,
							params: params,
							method: "_" + method
						});
					}
					else {
						self._CKBBSpecial();
					}
				}, self._hCKBBIntervalDelay);
			}, self._hCKBBTimeoutDelay);
		},


		// public method to scale selected canvas content
		scale: function (opts) {
			var self = this,
				elem = self.element,
				o = self.options,
				inputAlias = self._selectedCanvas.data('alias'),
				canvasCtrl = self._canvasController[inputAlias];

			if (canvasCtrl && !(canvasCtrl.type === 'text' && !o.font.resizable)) {
				opts.method = (opts.method === undefined) ? 'scale' : opts.method;
				self._continueHandler($.extend(opts, {
					self: self
				}));
			}

		},


		// public method to rotate selected canvas content
		rotate: function (opts) {
			var self = this,
				elem = self.element,
				o = self.options;

			opts.method = (opts.method === undefined) ? 'rotate' : opts.method;

			self._continueHandler($.extend(opts, {
				self: self
			}));
		},


		// public method to move selected canvas up or down
		layerUpDown: function (opts) {
			var self = this,
				elem = self.element,
				o = self.options;

			opts.method = (opts.method === undefined) ? 'layerUpDown' : opts.method;

			self._continueHandler($.extend(opts, {
				self: self
			}));
		},


		// method to scale the content of a selected canvas
		_scale: function () {
			var self = this.self,
				elem = self.element,
				o = self.options,
				params = this.params,
				method = this.method,
				factor = (1 + (params.factor / 100));

			if (self._selectedCanvas) {
				var inputAlias = self._selectedCanvas.data('alias'),
					canvasCtrl = self._canvasController[inputAlias],
					area = self._imageContainer.find('[data-position=' + canvasCtrl.area + ']'),
					newWidth = canvasCtrl.width * factor,
					newHeight = canvasCtrl.height * factor,
					isOk = Math.floor(canvasCtrl.center.abs_left - newWidth / 2) >= 0 && Math.ceil(canvasCtrl.center.abs_left + newWidth / 2) <= area.width() && Math.floor(canvasCtrl.center.abs_top - newHeight / 2) >= 0 && Math.ceil(canvasCtrl.center.abs_top + newHeight / 2) <= area.height();

				// if scale down and minimum values are reached -> return
				if (params.factor < 0 && (newWidth < o.minScale.width || newHeight < o.minScale.height * canvasCtrl.lines)) {
					return;
				}

				// if rotation is out of bounds or rotation is ok
				if ((self._errorObject['rotate'][inputAlias] !== undefined) || isOk) {
					if (isOk) {
						self._setValidState({
							element: self._selectedCanvas,
							invalid: false,
							type: 'rotate',
							inputAlias: inputAlias
						});
					}

					// update canvas controller with scaled values
					self._updateCanvasController(inputAlias, {
						width: newWidth,
						height: newHeight,
						top: (canvasCtrl.center.abs_top - newHeight / 2),
						left: (canvasCtrl.center.abs_left - newWidth / 2)
					});

					// position canvas
					self._selectedCanvas.css({
						top: canvasCtrl.top + 'px',
						left: canvasCtrl.left + 'px'
					});

					// update canvas controller type specific
					if (canvasCtrl.type === 'text') {
						self._updateCanvasController(inputAlias, {
							fontSize: canvasCtrl.fontSize * factor
						});
					}
					else if (canvasCtrl.type === 'file') {
						self._updateCanvasController(inputAlias, {
							innerWidth: canvasCtrl.innerWidth * factor,
							innerHeight: canvasCtrl.innerHeight * factor
						});
					}

					// redraw canvas with scaled content
					self._updateCanvas(inputAlias, {
						resizable: true
					});
				}
			}
		},


		// method to rotate the content of a selected canvas
		_rotate: function () {
			var self = this.self,
				elem = self.element,
				o = self.options,
				params = this.params;

			if (self._selectedCanvas) {
				var inputAlias = self._selectedCanvas.data('alias'),
					canvasCtrl = self._canvasController[inputAlias],
					area = self._imageContainer.find('[data-position=' + canvasCtrl.area + ']'),
					arcTan = Math.atan(canvasCtrl.innerWidth / canvasCtrl.innerHeight),
					diagonal = Math.sqrt(canvasCtrl.innerWidth * canvasCtrl.innerWidth + canvasCtrl.innerHeight * canvasCtrl.innerHeight),
					newAngle = canvasCtrl.angle + params.angle,
					argument = {},
					newWidth,
					newHeight;

				// hold angle in interval = [0 .. 360]
				newAngle = (newAngle >= 360) ? (newAngle - 360) : ((newAngle < 0) ? (newAngle + 360) : newAngle);

				// if angle is in second or forth quadrant
				if (newAngle > 90 && newAngle < 180 || newAngle > 270 && newAngle < 360) {
					argument.x = self._degToRad(newAngle) - arcTan - self._degToRad(90);
					argument.y = self._degToRad(180) - self._degToRad(newAngle) - arcTan;
					// if angle is in first or third quadrant
				}
				else {
					argument.x = self._degToRad(90) - self._degToRad(newAngle) - arcTan;
					argument.y = arcTan - self._degToRad(newAngle);
				}

				// calculate new dimensions after rotation
				newWidth = Math.round(Math.abs(Math.cos(argument.x) * diagonal));
				newHeight = Math.round(Math.abs(Math.cos(argument.y) * diagonal));

				// if new dimensions don't fit to area -> set faulty
				if (Math.floor(self._canvasController[inputAlias].center.abs_left - newWidth / 2) >= 0 && Math.ceil(self._canvasController[inputAlias].center.abs_left + newWidth / 2) <= area.width() && Math.floor(self._canvasController[inputAlias].center.abs_top - newHeight / 2) >= 0 && Math.ceil(self._canvasController[inputAlias].center.abs_top + newHeight / 2) <= area.height()) {
					self._setValidState({
						element: self._selectedCanvas,
						invalid: false,
						type: 'rotate',
						inputAlias: inputAlias
					});
				}
				else {
					self._setValidState({
						element: self._selectedCanvas,
						invalid: true,
						type: 'rotate',
						inputAlias: inputAlias
					});
				}

				// update canvas controller with rotated values
				self._updateCanvasController(inputAlias, {
					error: !! self._errorObject.rotate[inputAlias],
					angle: newAngle,
					width: (canvasCtrl.type === 'file') ? newWidth : self._selectedCanvas.width(),
					height: (canvasCtrl.type === 'file') ? newHeight : self._selectedCanvas.height(),
					top: (canvasCtrl.center.abs_top - newHeight / 2),
					left: (canvasCtrl.center.abs_left - newWidth / 2)
				});

				// redraw canvas with rotated content
				self._updateCanvas(inputAlias, {
					resizable: true
				});
				self._setTopLeft(inputAlias);
			}
		},


		// method to move a canvas a layer up or down
		_layerUpDown: function () {
			var self = this.self,
				elem = self.element,
				o = self.options,
				params = this.params;

			if (self._selectedCanvas) {
				var inputAlias = self._selectedCanvas.data('alias'),
					canvasCtrl = self._canvasController[inputAlias],
					area = self._imageContainer.find('[data-position=' + canvasCtrl.area + ']'),
					containingCanvas = area.find('canvas');

				// if new zIndex is valid
				if ((canvasCtrl.zIndex + params.step > o.zIndex) && (canvasCtrl.zIndex + params.step <= (o.zIndex + containingCanvas.length))) {
					// find canvas with adjacent zIndex
					containingCanvas.each(function (i) {
						var $this = $(this),
							innerInputAlias = $this.data('alias');

						// if current element is the selected canvas
						if ((this != self._selectedCanvas[0]) && (self._canvasController[innerInputAlias].zIndex == canvasCtrl.zIndex + params.step)) {
							var tempIndex = canvasCtrl.zIndex;

							// set new zIndex
							canvasCtrl.zIndex = self._canvasController[innerInputAlias].zIndex;
							// set zIndex of the adjacent element
							self._updateCanvasController(innerInputAlias, {
								zIndex: tempIndex
							});

							// swap css zIndices
							self._selectedCanvas.css({
								zIndex: canvasCtrl.zIndex
							});
							$this.css({
								zIndex: self._canvasController[innerInputAlias].zIndex
							});

							return false;
						}
					});
				}
			}
		},


		// removes an element (canvas from area AND entry from canvas controller)
		removeElement: function (opts) {
			var self = this,
				elem = self.element,
				o = self.options,
				inputAlias,
				canvasCtrl;

			// if a canvas is selected
			if (self._selectedCanvas) {
				inputAlias = self._selectedCanvas.data('alias');
				canvasCtrl = self._canvasController[inputAlias];

				// if already a canvas controller entry for the inputAlias exists
				if (canvasCtrl) {
					var area = self._imageContainer.find('[data-position=' + canvasCtrl.area + ']'),
						type = canvasCtrl.type,
						$input;

					self._reorganizeZIndex(area, canvasCtrl.zIndex);

					if (type === 'text') {
						$input = self._inputMask.find('.' + self._cssClassTextInput + '[data-alias=' + inputAlias + ']');

						$input.val('')
							.closest('div.' + self._cssClassInputElements)
							.find('.' + self._cssClassMaxLength)
							.html($input.data('length'));
					}
					else if (type === 'file') {
						$input = self._inputMask.find('.' + self._cssClassFileInput + '[data-alias=' + inputAlias + ']');
						self._inputMask.find(".ep-uiCustomizer-fileName[data-alias=" + inputAlias + "]")
							.html('');
						// if file is required -> set flag in error object
						if ($input.data('required')) {
							self._setValidState({
								element: $input,
								invalid: true,
								type: 'file',
								inputAlias: inputAlias
							});
						}

						// if already an image exists in the DOM/canvas controller
						if (canvasCtrl !== undefined && canvasCtrl.image !== undefined) {
							canvasCtrl.image.remove();
							delete canvasCtrl.image;
						}
					}

					// if marked as faulty -> remove error flag
					self._setValidState({
						element: self._selectedCanvas,
						invalid: false,
						type: 'rotate',
						inputAlias: inputAlias
					});


					if (this.remove === undefined) {
						// delete entry from canvas controller
						self._canvasController[inputAlias] = undefined;
						delete self._canvasController[inputAlias];
					}

					// remove canvas from DOM
					self._selectedCanvas.remove();
					self._selectedCanvas = undefined;

				}
				else if (self._selectedInput) {
					inputAlias = self._selectedInput;
					canvasCtrl = self._canvasController[inputAlias];

					if (canvasCtrl) {
						if (canvasCtrl.type === 'file') {
							$input = self._inputMask.find('.' + self._cssClassFileInput + '[data-alias=' + inputAlias + ']');
							self._inputMask.find(".ep-uiCustomizer-fileName[data-alias=" + inputAlias + "]")
								.html('');

							// if file is required -> set flag in error object
							if ($input.data('required')) {
								self._setValidState({
									element: $input,
									invalid: true,
									type: 'file',
									inputAlias: inputAlias
								});
							}

							// if already an image exists in the DOM/canvas controller
							if (canvasCtrl !== undefined && canvasCtrl.image !== undefined) {
								canvasCtrl.image.remove();
								delete canvasCtrl.image;
							}
						}

						if (this.remove === undefined) {
							// delete entry from canvas controller
							self._canvasController[inputAlias] = undefined;
							delete self._canvasController[inputAlias];
						}
					}
				}
			}
		},


		// remove canvas from DOM and update canvas controller with default values
		_removeCanvas: function (opts) {
			var self = this,
				elem = self.element,
				o = self.options;

			if (self._selectedCanvas) {
				var inputAlias = self._selectedCanvas.data('alias'),
					canvasCtrl = self._canvasController[inputAlias],
					area = self._imageContainer.find('[data-position=' + canvasCtrl.area + ']');

				self._reorganizeZIndex(area, canvasCtrl.zIndex);

				// if marked as faulty -> remove error flag
				self._setValidState({
					element: self._selectedCanvas,
					invalid: false,
					type: 'rotate',
					inputAlias: inputAlias
				});

				// if already an image exists in the DOM/canvas controller
				if (canvasCtrl !== undefined && canvasCtrl.image !== undefined) {
					canvasCtrl.image.remove();
					delete canvasCtrl.image;
				}

				// update canvas controller with default values
				self._updateCanvasController(inputAlias, {
					angle: 0,
					top: 0,
					left: 0,
					center: {
						rel_top: 0,
						rel_left: 0,
						abs_top: 0,
						abs_left: 0
					},
					innerWidth: 0,
					innerHeight: 0,
					width: 0,
					height: 0,
					recalc: true,
					preview: false
				});

				/* change_default */
				/*self._updateCanvasController(
          inputAlias,
          $.extend(
            {
              recalc: true,
              preview: false
            },
            self._defaultValues.base,
            self._defaultValues.extended
          )
        );*/

				// remove canvas from DOM
				self._selectedCanvas.remove();
			}
		},


		// creates a tooltip
		_initTooltip: function (options) {
			var type = options.type,
				context = options.context;

			this.tooltip[type] = $('<div>')
				.addClass('ep-uiInput-info ep-uiValidate-message')
				.uiTooltip({
				interactive: true,
				event: 'hover',
				hideDelay: 1400,
				context: context,
				orientation: 'right',
				offsetAdjust: [3, - 2]
			});
		},


		// sets the error state of an element
		_setValidState: function (options) {
			var self = this,
				elem = self.element,
				o = self.options,
				type = options.type, // "file", "rotate"
				inputAlias = options.inputAlias,
				invalid = options.invalid,
				element = options.element; // element (as jQuery object) which state should be set

			// write the valid state to the error object
			if (invalid) {
				self._errorObject[type][inputAlias] = invalid;
			}
			else if (self._errorObject[type][inputAlias] !== undefined) {
				delete self._errorObject[type][inputAlias];
			}

			$(element)
				.prop('formInvalid', invalid);

			// set error class for types listed in the array
			$.each(['rotate'], function (index, value) {
				if (type === value) {
					element[invalid ? 'addClass' : 'removeClass'](self._cssClassCanvasError);

					if (invalid) {
						// create a tooltip if necessary
						if (self.tooltip[value] === undefined) {
							self._initTooltip({
								context: $(element),
								type: value
							});
						}
						// update tooltip options
						self.tooltip[value].uiTooltip('option', 'context', $(element));
						self.tooltip[value].html(tOutOfBounds);
						self.tooltip[value].uiTooltip('show');

						// delete running timeouts to avoid multiple tooltip executions
						if (self._hTooltip !== undefined) {
							clearTimeout(self._hTooltip);
							self._hTooltip = undefined;
						}
						// trigger event on element to open the tooltip with delay
						self._hTooltip = setTimeout(function () {
							$(element)
								.trigger('mouseover');
						}, 200);
					}
					else {
						// destroy existing tooltip
						if (self.tooltip[value] !== undefined) {
							self.tooltip[value].uiTooltip('hide');
							self.tooltip[value].uiTooltip('destroy');
							self.tooltip[value].remove();
							delete self.tooltip[value];
						}
					}
				}
			});

			// test if there is a faulty canvas -> set hidden inputs formInvalid
			self._hiddenCanvasErrors.prop('formInvalid', false);
			if (o.productImg) {
				self._productPreview.find('canvas')
					.each(function () {
						if ($(this)
							.prop('formInvalid') !== undefined && $(this)
							.prop('formInvalid')) {
							self._hiddenCanvasErrors.prop('formInvalid', true);
							return false;
						}
					});
			}


			// if the form option is set and element is invalid or hidden error field is set -> trigger change event on form to start error handling
			if (o.form !== undefined) {
				// if invalid state was comitted OR a faulty canvas was found
				if (!invalid || self._hiddenCanvasErrors.prop('formInvalid')) {
					o.form.trigger('change');
				}
			}
		},


		// defines the size of the product image if it doesn't fit into the container
		_recalcDimensions: function (image, options) {
			var $image = $(image),
				factor,
				dimension = {
					w: $image.width(),
					h: $image.height()
				};

			// options.longer indicates if the image has to be prooved if one dimension (width or height) is to large for max values
			if (options.longer !== undefined) {
				if (options.maxWidth > options.maxHeight) {
					factor = options.maxWidth / dimension.w;
					dimension.w = options.maxWidth;
					dimension.h = factor * dimension.h;
				}
				else {
					factor = options.maxHeight / dimension.h;
					dimension.h = options.maxHeight;
					dimension.w = factor * dimension.w;
				}
			}
			else {
				if (dimension.w > options.maxWidth) {
					factor = options.maxWidth / dimension.w;
					dimension.w = options.maxWidth;
					dimension.h = factor * dimension.h;
				}

				if (dimension.h > options.maxHeight) {
					factor = options.maxHeight / dimension.h;
					dimension.h = options.maxHeight;
					dimension.w = factor * dimension.w;
				}
			}

			return dimension;
		},


		// convert an angle from degree to rad
		_degToRad: function (angle) {
			return angle * Math.PI / 180;
		},


		// public method to get a base64 encoded string of the customized image
		/**
		 * Returns a base64 encoded string containing the customized product image.
		 * 
		 * @method getBase64Image
		 * @member jQuery.ui.productUiCustomizer
		 * 
		 * @since 6.14.0
		 */
		getBase64Image: function () {
			var self = this,
				elem = self.element,
				o = self.options;

			if (self._base64Image === undefined) {
				self._generateImageFromPreview();
			}

			return self._base64Image;
		},


		// public method to get a base64 encoded string of the customized preview image
		/**
		 * Returns a base64 encoded string containing a small preview image of the customized product.
		 * 
		 * @method getBase64PreviewImage
		 * @member jQuery.ui.productUiCustomizer
		 * 
		 * @since 6.14.0
		 */
		getBase64PreviewImage: function () {
			var self = this,
				elem = self.element,
				o = self.options;

			if (self._base64PreviewImage === undefined) {
				self._generateImageFromPreview();
			}

			return self._base64PreviewImage;
		},


		// generates a base64 encoded string from the customized areas
		_generateImageFromPreview: function () {
			var self = this,
				elem = self.element,
				o = self.options;

			if (self.options.showPreview && !self._isOldIE) {
				// create canvas element
				var $canvas = $('<canvas />')
						.appendTo(self.element),
					inputAliasByZIndex = [],
					inputAlias,
					canvasCtrl;

				// sort aliases by zIndex (necessary to render canvas' in correct order
				for (inputAlias in self._canvasController) {
					if (inputAliasByZIndex[self._canvasController[inputAlias].zIndex - o.zIndex] === undefined) {
						inputAliasByZIndex[self._canvasController[inputAlias].zIndex - o.zIndex] = [];
					}
					inputAliasByZIndex[self._canvasController[inputAlias].zIndex - o.zIndex].push(inputAlias);
				}

				// set imageContainer dimensions to canvas element
				$canvas
					.attr({
						width: o.maxWidth,
						height: o.maxHeight
					})
					.css({
						display: 'none'
					});

				// insert product image to canvas
				if (o.productImg) {
					$canvas
						.canvas()
						.drawImage(self._productImage[0], 0.5 * ($canvas.width() - self._productImage.width()), 0.5 * ($canvas.height() - self._productImage.height()), ((self._productImage.width() < $canvas.width()) ? self._productImage.width() : $canvas.width()), ((self._productImage.height() < $canvas.height()) ? self._productImage.height() : $canvas.height()));
					//.drawImage(self._productImage[0], 0.5*($canvas.width()-self._productImage.width()), 0.5*($canvas.height()-self._productImage.height())); //, $canvas.width(), $canvas.height());
				}

				//for(var inputAlias in self._canvasController){
				for (var i = 1, iLength = inputAliasByZIndex.length; i < iLength; i++) {
					for (var j = 0, jLength = inputAliasByZIndex[i].length; j < jLength; j++) {
						inputAlias = inputAliasByZIndex[i][j];
						canvasCtrl = self._canvasController[inputAlias];

						// if canvas should be rendered to the preview image
						if (canvasCtrl.preview) {
							// render current canvas to preview canvas
							$canvas.canvas()
								.drawImage(self._imageContainer.find('canvas.' + inputAlias)[0], self._areaObject[canvasCtrl.area].left + canvasCtrl.left, self._areaObject[canvasCtrl.area].top + canvasCtrl.top);
						}
					}
				}

				// extract base64 encoded string from DataURL string
				self._base64Image = $canvas.canvas()
					.toDataURL()
					.split(",")[1];

				// clone canvas to become able to copy a smaller preview image
				var $clonedCanvas = $canvas.clone();
				// set preview image dimensions to canvas element
				$clonedCanvas.attr({
					width: o.prevWidth,
					height: o.prevHeight
				});

				// render canvas with preview demensions
				$clonedCanvas.canvas()
					.drawImage($canvas[0], 0, 0, o.prevWidth, o.prevHeight);
				// extract base64 encoded string with preview image information
				self._base64PreviewImage = $clonedCanvas.canvas()
					.toDataURL()
					.split(",")[1];

				$canvas.remove();
			}
			else {
				self._base64Image = '';
				self._base64PreviewImage = '';
			}
		},


		// public method - generates and returns a JSON object containing the customized data from all areas/canvas'
		/**
		 * Generates a JSON object containing the customized data.
		 * 
		 * @method generateCustomizedJSON
		 * @member jQuery.ui.productUiCustomizer
		 * 
		 * @since 6.14.0
		 */
		generateCustomizedJSON: function () {
			var self = this,
				elem = self.element,
				o = self.options,
				canvasCtrl = self._canvasController,
				base64CustomizedJSON = {};

			for (var key in canvasCtrl) {
				var alias = key.substr(1);
				base64CustomizedJSON[alias] = {
					Type: canvasCtrl[key].type,
					Height: canvasCtrl[key].height,
					Width: canvasCtrl[key].width,
					InnerHeight: canvasCtrl[key].innerHeight,
					InnerWidth: canvasCtrl[key].innerWidth,
					FromTop: isNaN(canvasCtrl[key].top) ? 0 : Math.round(canvasCtrl[key].top),
					FromLeft: isNaN(canvasCtrl[key].left) ? 0 : Math.round(canvasCtrl[key].left),
					ZIndex: canvasCtrl[key].zIndex,
					Orientation: canvasCtrl[key].angle,
					AreaID: canvasCtrl[key].area,
					ExistingLineItemID: self.options.existingLineItemID
				};

				// EPG-30278
				// required values may be undefined when uploading a PDF file
				$.each(['Height', 'Width', 'InnerHeight', 'InnerWidth', 'FromTop', 'FromLeft', 'ZIndex', 'Orientation'], 
					function(idx, val) {
						if(base64CustomizedJSON[alias][val] == null) {
							base64CustomizedJSON[alias][val] = 0;
						}
					}
				);

				if (canvasCtrl[key].type === "text") {
					$.extend(base64CustomizedJSON[alias], {
						FontSize: canvasCtrl[key].fontSize,
						Color: canvasCtrl[key].fontColor,
						FontName: canvasCtrl[key].fontFamily,
						Text: canvasCtrl[key].text
					});
				}
				else if (canvasCtrl[key].type === "file") {
					$.extend(base64CustomizedJSON[alias], {
						FileName: canvasCtrl[key].FileName,
						//						FilePath: canvasCtrl[key].FilePath,
						GUID: canvasCtrl[key].GUID
					});
				}
			}

			return ($.isEmptyObject(base64CustomizedJSON)) ? self.options.existingLineItemID : base64CustomizedJSON;
		},


		// public method - returns the GUID list (necessary to delete temporary uploaded files)
		/**
		 * Returns an array containing the GUIDs from all uploaded files.
		 * 
		 * @method getGUIDList
		 * @member jQuery.ui.productUiCustomizer
		 * 
		 * @since 6.14.0
		 */
		getGUIDList: function () {
			return this._cleanUpGUIDList;
		},

		//#JSCOVERAGE_IF false
		// public method - send the base64 encoded GUID list via ajax (necessary to delete temporary uploaded files)
		sendGUIDasJSON: function () {
			var _this = this;

			ep.ajax({
				dataType: 'json',
				data: {
					ViewAction: 'JSONViewResponse',
					ChangeObjectID: this.options.data.ChangeObjectID,
					ChangeAction: 'CleanUpTempCustomizedFiles',
					CleanupGUIDList: base64.encode(this._cleanUpGUIDList)
				}
			})
			.done(function (data) {
				// clean GUIDList
				_this._cleanUpGUIDList.length = 0;
			})
			.fail(function (xhr, status, error) {
				// ToDo: something more
				// console.warn(error);
			});
		},
		//#JSCOVERAGE_ENDIF

		// public method - clean up some stuff
		customDestroy: function () {
			var self = this,
				elem = self.element,
				o = self.options;

			for (var key in self._canvasController) {
				delete self._canvasController[key];
			}
			self._canvasController = undefined;
		}

	});

	return de_epages;

});

/*
 * *****************************************************************
 * the following html structure is created when using the customizer
 * *****************************************************************
 *

  <div id="customizerDialog" class="dialog notificationDialog ui-dialog-content ui-widget-content">
      <ul class="ep-uiCustomizer" id="customizer">
          <!-- contains product image and toolbar -->
          <li class="ep-uiCustomizer-preview">
              <!-- product image and customize areas
              <div class="ep-uiCustomizer-imageContainer">
                  <img class="ep-uiCustomizer-productImage" src="productImage.png">
                  <div id="customizerArea_1" data-type="area" data-position="1" class="ep-uiCustomizer-sortableArea ui-droppable">
                      <canvas class="_15555 ui-draggable ep-uiCustomizer-canvasHoverDraggable"></canvas>
                  </div>
                  <div id="customizerArea_2" data-type="area" data-position="2" class="ep-uiCustomizer-sortableArea ui-droppable"></div>
              </div>
              <!-- toolbar -->
              <ul class="ep-uiCustomizer-toolbar">
                  <li class="ep-uiCustomizer-scaleUp" data-key="scaleUp" data-method="scale" title="{Expand}"><span>&nbsp;</span></li>
                  <li class="ep-uiCustomizer-scaleDown" data-key="scaleDown" data-method="scale" title="{Downsize}"><span>&nbsp;</span></li>
                  <!-- more buttons -->
              </ul>
          </li>
          <!-- contains input elements (text, file, color, font family and so on) -->
          <li class="ep-uiCustomizer-inputs">
              <h2>head</h2>
              <div>additional information</div>
              <div class="ep-uiCustomizer-description">description</div>
              <!-- example: text input -->
              <div id="customizerInput__15554" class="ep-uiCustomizer-inputElements ep-uiCustomizer-inputSelected" data-alias="_15554">
                  <h4>input head</h4>
                  <label class="ep-uiInput-wrap ep-uiInput-wrapText">
                      <input class="ep-uiCustomizer-textInput FullWidth ep-uiInput ep-uiInput-base ep-uiInput-field ep-uiInput-text epWidth-4" data-alias="_15554" data-length="100" type="text">
                  </label>
                  <div class="ep-uiCustomizer-RemainingCharacters">RemainingCharacters
                      <span class="ep-uiCustomizer-maxLength">100</span>
                  </div>
                  <select class="ep-uiCustomizer-fontFamily" data-alias="_15554">
                      <option selected="selected" value="Arial" style="font-family: Arial;">Arial</option>
                      <option value="Courier New" style="font-family: Courier New;">Courier New</option>
                      <!-- more options -->
                  </select>
                  <ul class="ep-uiCustomizer-fontColor" data-alias="_15554">
                      <li data-color="black" data-selected="1" class="selectedColor">&nbsp;</li>
                      <li data-color="#00a0ff" data-selected="0">&nbsp;</li>
                      <!-- more colors -->
                  </ul>
                  <br class="ClearLeft">
              </div>
              <!-- example: file input -->
              <div id="customizerInput__15555" class="ep-uiCustomizer-inputElements" data-alias="_15555">
                  <h4>input head</h4>
                  <label class="ep-uiInput-wrap de_epages-presentationUiUploader-button">
                      <span class="ep-uiInput ep-uiInput-base ep-uiInput-button">
                          <span class="ep-sprite ep-sprite-s ico_s_fileupload"></span>Upload file</span>
                      <input accept="image/jpeg,image/png,image/gif,application/pdf" class="ep-uiCustomizer-fileInput ep-uiInput-hidden" data-alias="_15555" type="file">
                  </label>
                  <div class="ep-uiCustomizer-fileName" data-alias="_15555">UploadedFileName.png</div>
                  <div class="ep-uiCustomizer-ImageUploadRestrictions">SupportedFileFormats: jpg,png,gif,pdf<br />
                      MinSize: 200 px x 200 px
                  </div>
              </div>
          </li>
      </ul>
      <input class="" name="ChangeObjectID" value="15292" type="hidden">
      <input class="" name="ChangeAction" value="AddCustomizedProductToBasket" type="hidden">
      <input class="" name="ViewObjectID" value="15292" type="hidden">
      <input class="" name="ViewAction" value="View" type="hidden">
      <input class="" name="Base64Image" value="" type="hidden">
      <input class="" name="Base64PreviewImage" value="" type="hidden">
      <input class="" name="Base64CustomizedAreas" value="" type="hidden">
      <!-- element for quantity like spinner oder text input -->
      <input name="epCustomizerCanvasErrors" type="hidden"><!-- hidden input to handle canvas errors -->
  </div>
*/