/** * 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.18 $ */ 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", "util/browser", "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.prop('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> </span></li> <li class="ep-uiCustomizer-scaleDown" data-key="scaleDown" data-method="scale" title="{Downsize}"><span> </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"> </li> <li data-color="#00a0ff" data-selected="0"> </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> */