1 /**
  2  * @file Array
  3  *
  4  * @author Dominik Kocuj
  5  * @license http://www.gnu.org/licenses/gpl-2.0.html GNU General Public License v2 or later
  6  * @copyright Copyright (c) 2016-2018 kocuj.pl
  7  */
  8 
  9 (function() {})(); // empty function for correct minify with comments
 10 //'use strict'; // for jshint uncomment this and comment line above
 11 
 12 /* jshint strict: true */
 13 /* jshint -W034 */
 14 
 15 /* global document */
 16 /* global jQuery */
 17 
 18 /* global kocujILV12aHelper */
 19 /* global kocujILV12aDataHelper */
 20 
 21 /* global kocujILV12aBackendSettingsFormArrayVals */
 22 
 23 /**
 24  * Array prototype constructor
 25  *
 26  * @constructs
 27  * @namespace kocujILV12aCBackendSettingsFormArray
 28  * @public
 29  * @return {void}
 30  */
 31 function kocujILV12aCBackendSettingsFormArray() {
 32 	'use strict';
 33 	/* jshint validthis: true */
 34 	// get this object
 35 	var self = this;
 36 	// initialize objects
 37 	self._objHelper = kocujILV12aHelper;
 38 	self._objDataHelper = kocujILV12aDataHelper;
 39 	// get current script filename
 40 	self._thisFilename = document.scripts[document.scripts.length-1].src;
 41 	// get settings
 42 	var vals = kocujILV12aBackendSettingsFormArrayVals;
 43 	if (vals.throwErrors === '1') {
 44 		self._valsThrowErrors = true;
 45 	} else {
 46 		self._valsThrowErrors = false;
 47 	}
 48 	self._valsHtmlIdFormatArrayContainer = vals.htmlIdFormatArrayContainer;
 49 	self._valsTextButtonAddNewElement = vals.textButtonAddNewElement;
 50 	self._valsDivElementContainer = vals.divElementContainer;
 51 	self._valsDivField = vals.divField;
 52 	self._valsDivControls = vals.divControls;
 53 	self._valsImageArrowDown = vals.imageArrowDown;
 54 	self._valsImageArrowUp = vals.imageArrowUp;
 55 	self._valsImageDelete = vals.imageDelete;
 56 	self._valsImageEmpty = vals.imageEmpty;
 57 }
 58 
 59 /**
 60  * Array prototype
 61  *
 62  * @namespace kocujILV12aCBackendSettingsFormArray
 63  * @public
 64  */
 65 kocujILV12aCBackendSettingsFormArray.prototype = {
 66 	/**
 67 	 * Object kocujILV12aHelper
 68 	 *
 69 	 * @private
 70 	 * @type {Object}
 71 	 */
 72 	_objHelper : null,
 73 
 74 	/**
 75 	 * Object kocujILV12aDataHelper
 76 	 *
 77 	 * @private
 78 	 * @type {Object}
 79 	 */
 80 	_objDataHelper : null,
 81 
 82 	/**
 83 	 * Current script filename
 84 	 *
 85 	 * @private
 86 	 * @type {string}
 87 	 */
 88 	_thisFilename : '',
 89 
 90 	/**
 91 	 * Projects list
 92 	 *
 93 	 * @private
 94 	 * @type {Array}
 95 	 */
 96 	_prj : [],
 97 
 98 	/**
 99 	 * Script settings - throw errors (true) or not (false)
100 	 *
101 	 * @private
102 	 * @type {string}
103 	 */
104 	_valsThrowErrors : false,
105 
106 	/**
107 	 * Script settings - HTML identifier format for array container
108 	 *
109 	 * @private
110 	 * @type {string}
111 	 */
112 	_valsHtmlIdFormatArrayContainer : '',
113 
114 	/**
115 	 * Script settings - text for button for adding new element
116 	 *
117 	 * @private
118 	 * @type {string}
119 	 */
120 	_valsTextButtonAddNewElement : '',
121 
122 	/**
123 	 * Script settings - div for element container
124 	 *
125 	 * @private
126 	 * @type {string}
127 	 */
128 	_valsDivElementContainer : '',
129 
130 	/**
131 	 * Script settings - div for element field
132 	 *
133 	 * @private
134 	 * @type {string}
135 	 */
136 	_valsDivField : '',
137 
138 	/**
139 	 * Script settings - div for element controls
140 	 *
141 	 * @private
142 	 * @type {string}
143 	 */
144 	_valsDivControls : '',
145 
146 	/**
147 	 * Script settings - image with down arrow
148 	 *
149 	 * @private
150 	 * @type {string}
151 	 */
152 	_valsImageArrowDown : '',
153 
154 	/**
155 	 * Script settings - image with up arrow
156 	 *
157 	 * @private
158 	 * @type {string}
159 	 */
160 	_valsImageArrowUp : '',
161 
162 	/**
163 	 * Script settings - image with delete button
164 	 *
165 	 * @private
166 	 * @type {string}
167 	 */
168 	_valsImageDelete : '',
169 
170 	/**
171 	 * Script settings - empty image
172 	 *
173 	 * @private
174 	 * @type {string}
175 	 */
176 	_valsImageEmpty : '',
177 
178 	/**
179 	 * Add project
180 	 *
181 	 * @public
182 	 * @param {string} projectId Project identifier
183 	 * @param {string} [projectName] Project name
184 	 * @return {void}
185 	 * @throws {kocujILV12aCException} kocujILV12aExceptionCode.PROJECT_ALREADY_EXISTS if project identifier entered in projectId already exists
186 	 */
187 	addProject : function(projectId, projectName) {
188 		'use strict';
189 		// parse arguments
190 		var args = this._checkAddProject(projectId, projectName);
191 		// add project
192 		if (this._prj['prj_' + args.projectId] === undefined) {
193 			this.addProjectIfNotExists(args.projectId, args.projectName);
194 		} else {
195 			this._throwError('PROJECT_ALREADY_EXISTS', args.projectId);
196 			return;
197 		}
198 	},
199 
200 	/**
201 	 * Add project if not exists
202 	 *
203 	 * @public
204 	 * @param {string} projectId Project identifier
205 	 * @param {string} [projectName] Project name
206 	 * @return {void}
207 	 */
208 	addProjectIfNotExists : function(projectId, projectName) {
209 		'use strict';
210 		// parse arguments
211 		var args = this._checkAddProject(projectId, projectName);
212 		// add project
213 		if (this._prj['prj_' + args.projectId] === undefined) {
214 			this._prj['prj_' + args.projectId] = {
215 				projectName : args.projectName,
216 				arrays      : []
217 			};
218 		}
219 	},
220 
221 	/**
222 	 * Get HTML selector for array container
223 	 *
224 	 * @public
225 	 * @param {string} projectId Project identifier
226 	 * @param {string} formId Form identifier
227 	 * @param {string} fieldId Field identifier
228 	 * @return {string} HTML selector for array container
229 	 */
230 	getHTMLSelectorArrayContainer : function(projectId, formId, fieldId) {
231 		'use strict';
232 		// parse arguments
233 		projectId = this._parseProjectId(projectId);
234 		formId = this._objHelper.initString(formId);
235 		fieldId = this._objHelper.initString(fieldId);
236 		// exit
237 		return '#' + this._getHTMLNameArrayContainer(projectId, formId, fieldId);
238 	},
239 
240 	/**
241 	 * Get HTML selector for array element container
242 	 *
243 	 * @public
244 	 * @param {string} projectId Project identifier
245 	 * @param {string} formId Form identifier
246 	 * @param {string} fieldId Field identifier
247 	 * @param {number} number Number of element in array
248 	 * @return {string} HTML selector for array element container
249 	 */
250 	getHTMLSelectorArrayElementContainer : function(projectId, formId, fieldId, number) {
251 		'use strict';
252 		// parse arguments
253 		projectId = this._parseProjectId(projectId);
254 		formId = this._objHelper.initString(formId);
255 		fieldId = this._objHelper.initString(fieldId);
256 		number = this._objHelper.initNumeric(number);
257 		// exit
258 		return '#' + this._getHTMLNameArrayContainer(projectId, formId, fieldId) + ' div[data-type="element-container"][data-number="' + number + '"]';
259 	},
260 
261 	/**
262 	 * Get HTML selector for array empty element container
263 	 *
264 	 * @public
265 	 * @param {string} projectId Project identifier
266 	 * @param {string} formId Form identifier
267 	 * @param {string} fieldId Field identifier
268 	 * @return {string} HTML selector for array empty element container
269 	 */
270 	getHTMLSelectorArrayEmptyElementContainer : function(projectId, formId, fieldId) {
271 		'use strict';
272 		// parse arguments
273 		projectId = this._parseProjectId(projectId);
274 		formId = this._objHelper.initString(formId);
275 		fieldId = this._objHelper.initString(fieldId);
276 		// exit
277 		return '#' + this._getHTMLNameArrayContainer(projectId, formId, fieldId) + ' div[data-type="element-container"][data-next="na"]';
278 	},
279 
280 	/**
281 	 * Get HTML selector for array element container field div
282 	 *
283 	 * @public
284 	 * @param {string} projectId Project identifier
285 	 * @param {string} formId Form identifier
286 	 * @param {string} fieldId Field identifier
287 	 * @param {number} number Number of element in array
288 	 * @return {string} HTML selector for array element container field div
289 	 */
290 	getHTMLSelectorArrayElementContainerFieldDiv : function(projectId, formId, fieldId, number) {
291 		'use strict';
292 		// parse arguments
293 		projectId = this._parseProjectId(projectId);
294 		formId = this._objHelper.initString(formId);
295 		fieldId = this._objHelper.initString(fieldId);
296 		number = this._objHelper.initNumeric(number);
297 		// exit
298 		return this.getHTMLSelectorArrayElementContainer(projectId, formId, fieldId, number) + ' div[data-type="field"]';
299 	},
300 
301 	/**
302 	 * Get HTML selector for array element container field value
303 	 *
304 	 * @public
305 	 * @param {string} projectId Project identifier
306 	 * @param {string} formId Form identifier
307 	 * @param {string} fieldId Field identifier
308 	 * @param {number} number Number of element in array
309 	 * @return {string} HTML selector for array element container field value
310 	 */
311 	getHTMLSelectorArrayElementContainerFieldValue : function(projectId, formId, fieldId, number) {
312 		'use strict';
313 		// parse arguments
314 		projectId = this._parseProjectId(projectId);
315 		formId = this._objHelper.initString(formId);
316 		fieldId = this._objHelper.initString(fieldId);
317 		number = this._objHelper.initNumeric(number);
318 		// exit
319 		return this.getHTMLSelectorArrayElementContainer(projectId, formId, fieldId, number) + ' [data-type="field-value"]';
320 	},
321 
322 	/**
323 	 * Get HTML selector for array element container controls div
324 	 *
325 	 * @public
326 	 * @param {string} projectId Project identifier
327 	 * @param {string} formId Form identifier
328 	 * @param {string} fieldId Field identifier
329 	 * @param {number} number Number of element in array
330 	 * @return {string} HTML selector for array element container controls div
331 	 */
332 	getHTMLSelectorArrayElementContainerControlsDiv : function(projectId, formId, fieldId, number) {
333 		'use strict';
334 		// parse arguments
335 		projectId = this._parseProjectId(projectId);
336 		formId = this._objHelper.initString(formId);
337 		fieldId = this._objHelper.initString(fieldId);
338 		number = this._objHelper.initNumeric(number);
339 		// exit
340 		return this.getHTMLSelectorArrayElementContainer(projectId, formId, fieldId, number) + ' div[data-type="controls"]';
341 	},
342 
343 	/**
344 	 * Get HTML selector for array element down arrow
345 	 *
346 	 * @public
347 	 * @param {string} projectId Project identifier
348 	 * @param {string} formId Form identifier
349 	 * @param {string} fieldId Field identifier
350 	 * @param {number} number Number of element in array
351 	 * @return {string} HTML selector for array element down arrow
352 	 */
353 	getHTMLSelectorArrayElementArrowDown : function(projectId, formId, fieldId, number) {
354 		'use strict';
355 		// parse arguments
356 		projectId = this._parseProjectId(projectId);
357 		formId = this._objHelper.initString(formId);
358 		fieldId = this._objHelper.initString(fieldId);
359 		number = this._objHelper.initNumeric(number);
360 		// exit
361 		return this.getHTMLSelectorArrayElementContainer(projectId, formId, fieldId, number) + ' div[data-type="controls"] a[data-type="down"]';
362 	},
363 
364 	/**
365 	 * Get HTML selector for array element up arrow
366 	 *
367 	 * @public
368 	 * @param {string} projectId Project identifier
369 	 * @param {string} formId Form identifier
370 	 * @param {string} fieldId Field identifier
371 	 * @param {number} number Number of element in array
372 	 * @return {string} HTML selector for array element up arrow
373 	 */
374 	getHTMLSelectorArrayElementArrowUp : function(projectId, formId, fieldId, number) {
375 		'use strict';
376 		// parse arguments
377 		projectId = this._parseProjectId(projectId);
378 		formId = this._objHelper.initString(formId);
379 		fieldId = this._objHelper.initString(fieldId);
380 		number = this._objHelper.initNumeric(number);
381 		// exit
382 		return this.getHTMLSelectorArrayElementContainer(projectId, formId, fieldId, number) + ' div[data-type="controls"] a[data-type="up"]';
383 	},
384 
385 	/**
386 	 * Get HTML selector for array element delete
387 	 *
388 	 * @public
389 	 * @param {string} projectId Project identifier
390 	 * @param {string} formId Form identifier
391 	 * @param {string} fieldId Field identifier
392 	 * @param {number} number Number of element in array
393 	 * @return {string} HTML selector for array element delete
394 	 */
395 	getHTMLSelectorArrayElementDelete : function(projectId, formId, fieldId, number) {
396 		'use strict';
397 		// parse arguments
398 		projectId = this._parseProjectId(projectId);
399 		formId = this._objHelper.initString(formId);
400 		fieldId = this._objHelper.initString(fieldId);
401 		number = this._objHelper.initNumeric(number);
402 		// exit
403 		return this.getHTMLSelectorArrayElementContainer(projectId, formId, fieldId, number) + ' div[data-type="controls"] a[data-type="delete"]';
404 	},
405 
406 	/**
407 	 * Process array elements
408 	 *
409 	 * @public
410 	 * @param {string} projectId Project identifier
411 	 * @param {string} formId Form identifier
412 	 * @param {string} fieldId Field identifier
413 	 * @param {string} emptyField Empty field
414 	 * @param {number} elementsCount Array elements count
415 	 * @param {Object} arraySettings Array settings
416 	 * @return {void}
417 	 */
418 	process : function(projectId, formId, fieldId, emptyField, elementsCount, arraySettings) {
419 		'use strict';
420 		// get this object
421 		var self = this;
422 		(function($) {
423 			// parse parameters
424 			projectId = self._parseProjectId(projectId);
425 			formId = self._objHelper.initString(formId);
426 			fieldId = self._objHelper.initString(fieldId);
427 			emptyField = self._objHelper.initString(emptyField);
428 			elementsCount = self._objHelper.initNumeric(elementsCount);
429 			arraySettings = self._objHelper.initObject(arraySettings);
430 			// remember array of fields
431 			var id = self._prj['prj_' + projectId].arrays.length;
432 			self._prj['prj_' + projectId].arrays['arr_' + id] = {
433 				formId        : formId,
434 				fieldId       : fieldId,
435 				arraySettings : arraySettings,
436 				emptyField    : emptyField,
437 				newNumber     : elementsCount
438 			};
439 			self._objDataHelper.setDataInDom($(self.getHTMLSelectorArrayContainer(projectId, formId, fieldId)), 'array-id', id);
440 			// update controls in fields
441 			for (var z=0; z<elementsCount; z++) {
442 				var prevNumber = '';
443 				if (z > 0) {
444 					prevNumber = z-1;
445 				}
446 				var nextNumber = '';
447 				if (z < elementsCount-1) {
448 					nextNumber = z+1;
449 				}
450 				self._updateControls(projectId, id, z, true, prevNumber, true, nextNumber);
451 			}
452 			// optionally add empty field
453 			if (arraySettings.autoadddeleteifempty) {
454 				self._addNewEmptyField(projectId, formId, fieldId);
455 			}
456 			// optionally add button for adding a new field
457 			if (arraySettings.addnewbutton) {
458 				// get selector
459 				var selectorArrayContainer = $(self.getHTMLSelectorArrayContainer(projectId, formId, fieldId));
460 				// add button
461 				selectorArrayContainer.append('<div data-type="add-new-button"><input type="button" class="button" value="' + self._valsTextButtonAddNewElement + '" /></div>');
462 				// add button event
463 				selectorArrayContainer.find('div[data-type="add-new-button"] input').bind('click', {
464 					self      : self,
465 					projectId : projectId,
466 					formId    : formId,
467 					fieldId   : fieldId
468 				}, function(event) {
469 					// disable default event
470 					event.preventDefault();
471 					// add new empty field if there is none
472 					if ($(event.data.self.getHTMLSelectorArrayEmptyElementContainer(event.data.projectId, event.data.formId, event.data.fieldId)).length === 0) {
473 						event.data.self._addNewEmptyField(event.data.projectId, event.data.formId, event.data.fieldId);
474 						$(this).insertAfter($(event.data.self.getHTMLSelectorArrayEmptyElementContainer(event.data.projectId, event.data.formId, event.data.fieldId)));
475 					}
476 				});
477 			}
478 		}(jQuery));
479 	},
480 
481 	/**
482 	 * Get HTML array container name
483 	 *
484 	 * @private
485 	 * @param {string} projectId Project identifier
486 	 * @param {string} formId Form identifier
487 	 * @param {string} fieldId Field identifier
488 	 * @param {number} number Number of element in array
489 	 * @return {string} HTML array container name
490 	 */
491 	_getHTMLNameArrayContainer : function(projectId, formId, fieldId) {
492 		'use strict';
493 		// exit
494 		return this._valsHtmlIdFormatArrayContainer.replace('####FORM_ID####', formId).replace('####FIELD_ID####', fieldId);
495 	},
496 
497 	/**
498 	 * Parse project identifier
499 	 *
500 	 * @private
501 	 * @param {string} projectId Project identifier
502 	 * @return {string} Parsed project identifier
503 	 * @throws {kocujILV12aCException} kocujILV12aExceptionCode.EMPTY_PROJECT_ID if project identifier entered in projectId is empty
504 	 * @throws {kocujILV12aCException} kocujILV12aExceptionCode.PROJECT_DOES_NOT_EXIST if project identifier entered in projectId does not exist
505 	 */
506 	_parseProjectId : function(projectId) {
507 		'use strict';
508 		// parse project identifier
509 		projectId = this._objHelper.initString(projectId);
510 		if (projectId === '') {
511 			this._throwError('EMPTY_PROJECT_ID');
512 			return;
513 		}
514 		// check if project exists
515 		if (this._prj['prj_' + projectId] === undefined) {
516 			this._throwError('PROJECT_DOES_NOT_EXIST', projectId);
517 			return;
518 		}
519 		// exit
520 		return projectId;
521 	},
522 
523 	/**
524 	 * Check arguments for adding project
525 	 *
526 	 * @private
527 	 * @param {string} projectId Project identifier
528 	 * @param {string} [projectName] Project name
529 	 * @return {Object} Parsed arguments for adding project
530 	 * @throws {kocujILV12aCException} kocujILV12aExceptionCode.EMPTY_PROJECT_ID if project identifier entered in projectId is empty
531 	 */
532 	_checkAddProject : function(projectId, projectName) {
533 		'use strict';
534 		// parse arguments
535 		projectId = this._objHelper.initString(projectId);
536 		if (projectId === '') {
537 			this._throwError('EMPTY_PROJECT_ID');
538 			return;
539 		}
540 		projectName = this._objHelper.initString(projectName);
541 		// exit
542 		return {
543 			projectId   : projectId,
544 			projectName : projectName
545 		};
546 	},
547 
548 	/**
549 	 * Throw an error if debugging is enabled
550 	 *
551 	 * @private
552 	 * @param {string} codeString Error code in string format
553 	 * @param {string} [param] Parameter for error information
554 	 * @return {void}
555 	 */
556 	_throwError : function(codeString, param) {
557 		'use strict';
558 		// parse arguments
559 		codeString = this._objHelper.initString(codeString);
560 		if (codeString === '') {
561 			return;
562 		}
563 		param = this._objHelper.initString(param);
564 		// throw an error
565 		if (this._valsThrowErrors) {
566 			/* jshint evil: true */
567 			eval('throw new kocujILV12aCException(kocujILV12aExceptionCode.' + codeString + ', this._thisFilename, param);');
568 		}
569 	},
570 
571 	/**
572 	 * Update controls
573 	 *
574 	 * @private
575 	 * @param {string} projectId Project identifier
576 	 * @param {number} arrayId Identifier of array of fields
577 	 * @param {number} number Number of element in array
578 	 * @param {boolean} changePrevNumber Change previous number (true) or leave it as it is (false)
579 	 * @param {number|string} prevNumber Previous number or empty string if there is none
580 	 * @param {boolean} changeNextNumber Change next number (true) or leave it as it is (false)
581 	 * @param {number|string} nextNumber Next number or empty string if there is none
582 	 * @return {void}
583 	 */
584 	_updateControls : function(projectId, arrayId, number, changePrevNumber, prevNumber, changeNextNumber, nextNumber) {
585 		'use strict';
586 		// get this object
587 		var self = this;
588 		(function($) {
589 			// get array data
590 			var arrayData = self._prj['prj_' + projectId].arrays['arr_' + arrayId];
591 			// clear controls
592 			var selectorArrayElementArrowDown = $(self.getHTMLSelectorArrayElementArrowDown(projectId, arrayData.formId, arrayData.fieldId, number));
593 			var selectorArrayElementArrowUp = $(self.getHTMLSelectorArrayElementArrowUp(projectId, arrayData.formId, arrayData.fieldId, number));
594 			var selectorArrayElementDelete = $(self.getHTMLSelectorArrayElementDelete(projectId, arrayData.formId, arrayData.fieldId, number));
595 			if (selectorArrayElementArrowDown.length > 0) {
596 				selectorArrayElementArrowDown.remove();
597 			}
598 			if (selectorArrayElementArrowUp.length > 0) {
599 				selectorArrayElementArrowUp.remove();
600 			}
601 			if (selectorArrayElementDelete.length > 0) {
602 				selectorArrayElementDelete.remove();
603 			}
604 			// get array settings
605 			var arraySettings = arrayData.arraySettings;
606 			// get array element container selector
607 			var selectorArrayElementContainer = $(self.getHTMLSelectorArrayElementContainer(projectId, arrayData.formId, arrayData.fieldId, number));
608 			// get array element container controls selector
609 			var selectorArrayElementContainerControls = $(self.getHTMLSelectorArrayElementContainerControlsDiv(projectId, arrayData.formId, arrayData.fieldId, number));
610 			// add data attributes to array element container for controls
611 			self._objDataHelper.setDataInDom(selectorArrayElementContainer, 'number', number);
612 			if (changePrevNumber) {
613 				self._objDataHelper.setDataInDom(selectorArrayElementContainer, 'prev', prevNumber);
614 			}
615 			if (changeNextNumber) {
616 				self._objDataHelper.setDataInDom(selectorArrayElementContainer, 'next', nextNumber);
617 			}
618 			// prepare buttons link
619 			var buttonsLink = '<a href="#" data-type="####DATA_TYPE####"><img src="####IMAGE_URL####" alt="" style="float:left;border-width:0;border-style:none;" /></a>';
620 			// add down arrow
621 			var selector = '';
622 			var imageUrl = self._valsImageEmpty;
623 			if ((arraySettings.allowchangeorder) && (selectorArrayElementContainer.data('next') !== '')) {
624 				selector = self.getHTMLSelectorArrayElementArrowDown(projectId, arrayData.formId, arrayData.fieldId, number);
625 				imageUrl = self._valsImageArrowDown;
626 			}
627 			selectorArrayElementContainerControls.append(buttonsLink.replace('####DATA_TYPE####', 'down').replace('####IMAGE_URL####', imageUrl));
628 			if (selector !== '') {
629 				$(selector).bind('click.element', {
630 					self      : self,
631 					projectId : projectId,
632 					formId    : arrayData.formId,
633 					fieldId   : arrayData.fieldId
634 				}, function(event) {
635 					// disable default event
636 					event.preventDefault();
637 					// change HTML contents
638 					event.data.self._changeFields(event.data.projectId, event.data.formId, event.data.fieldId, parseInt($(this).parent().parent().data('number'), 10), $(this).parent().parent().data('next'));
639 				});
640 			}
641 			// add up arrow
642 			selector = '';
643 			imageUrl = self._valsImageEmpty;
644 			if ((arraySettings.allowchangeorder) && (selectorArrayElementContainer.data('prev') !== '')) {
645 				selector = self.getHTMLSelectorArrayElementArrowUp(projectId, arrayData.formId, arrayData.fieldId, number);
646 				imageUrl = self._valsImageArrowUp;
647 			}
648 			selectorArrayElementContainerControls.append(buttonsLink.replace('####DATA_TYPE####', 'up').replace('####IMAGE_URL####', imageUrl));
649 			if (selector !== '') {
650 				$(selector).bind('click.element', {
651 					self      : self,
652 					projectId : projectId,
653 					formId    : arrayData.formId,
654 					fieldId   : arrayData.fieldId
655 				}, function(event) {
656 					// disable default event
657 					event.preventDefault();
658 					// change HTML contents
659 					event.data.self._changeFields(event.data.projectId, event.data.formId, event.data.fieldId, $(this).parent().parent().data('prev'), parseInt($(this).parent().parent().data('number'), 10));
660 				});
661 			}
662 			// add delete button
663 			selector = '';
664 			imageUrl = self._valsImageEmpty;
665 			if (arraySettings.deletebutton) {
666 				selector = self.getHTMLSelectorArrayElementDelete(projectId, arrayData.formId, arrayData.fieldId, number);
667 				imageUrl = self._valsImageDelete;
668 			}
669 			selectorArrayElementContainerControls.append(buttonsLink.replace('####DATA_TYPE####', 'delete').replace('####IMAGE_URL####', imageUrl));
670 			if (selector !== '') {
671 				$(selector).bind('click.element', {
672 					self      : self,
673 					projectId : projectId,
674 					formId    : arrayData.formId,
675 					fieldId   : arrayData.fieldId
676 				}, function(event) {
677 					// disable default event
678 					event.preventDefault();
679 					// delete field
680 					event.data.self._deleteField(event.data.projectId, event.data.formId, event.data.fieldId, parseInt($(this).parent().parent().data('number'), 10));
681 				});
682 			}
683 			// add automatic adding and deleting of elements
684 			if (arraySettings.autoadddeleteifempty) {
685 				// add automatic deleting of element
686 				$(self.getHTMLSelectorArrayElementContainerFieldValue(projectId, arrayData.formId, arrayData.fieldId, number)).bind('change.element', {
687 					self      : self,
688 					projectId : projectId,
689 					formId    : arrayData.formId,
690 					fieldId   : arrayData.fieldId
691 				}, function(event) {
692 					// disable default event
693 					event.preventDefault();
694 					// optionally delete field
695 					if ($(this).val() === '') {
696 						event.data.self._deleteField(event.data.projectId, event.data.formId, event.data.fieldId, parseInt($(this).parent().parent().data('number'), 10));
697 					}
698 				});
699 			}
700 		}(jQuery));
701 	},
702 
703 	/**
704 	 * Change fields in array
705 	 *
706 	 * @private
707 	 * @param {string} projectId Project identifier
708 	 * @param {string} formId Form identifier
709 	 * @param {string} fieldId Field identifier
710 	 * @param {number} numberAbove First number of element in array
711 	 * @param {number} numberBelow Second number of element in array
712 	 * @return {void}
713 	 */
714 	_changeFields : function(projectId, formId, fieldId, numberAbove, numberBelow) {
715 		'use strict';
716 		// get this object
717 		var self = this;
718 		(function($) {
719 			// get array identifier
720 			var arrayId = $(self.getHTMLSelectorArrayContainer(projectId, formId, fieldId)).data('array-id');
721 			// get selectors
722 			var selectorArrayElementContainer1 = $(self.getHTMLSelectorArrayElementContainer(projectId, formId, fieldId, numberAbove));
723 			var selectorArrayElementContainer2 = $(self.getHTMLSelectorArrayElementContainer(projectId, formId, fieldId, numberBelow));
724 			// change positions
725 			selectorArrayElementContainer1.insertAfter(selectorArrayElementContainer2);
726 			// get previous and next numbers
727 			var prev1 = selectorArrayElementContainer1.data('prev');
728 			var prev2 = selectorArrayElementContainer2.data('prev');
729 			var next1 = selectorArrayElementContainer1.data('next');
730 			var next2 = selectorArrayElementContainer2.data('next');
731 			// update controls for previous array element
732 			if (prev1 !== '') {
733 				self._updateControls(projectId, arrayId, prev1, false, '', true, numberBelow);
734 			}
735 			// update controls for next array element
736 			if (next2 !== '') {
737 				self._updateControls(projectId, arrayId, next2, true, numberAbove, false, '');
738 			}
739 			// update controls
740 			self._updateControls(projectId, arrayId, numberAbove, true, numberBelow, true, next2);
741 			self._updateControls(projectId, arrayId, numberBelow, true, prev1, true, numberAbove);
742 		}(jQuery));
743 	},
744 
745 	/**
746 	 * Delete field in array
747 	 *
748 	 * @private
749 	 * @param {string} projectId Project identifier
750 	 * @param {string} formId Form identifier
751 	 * @param {string} fieldId Field identifier
752 	 * @param {number} number Number of element in array
753 	 * @return {void}
754 	 */
755 	_deleteField : function(projectId, formId, fieldId, number) {
756 		'use strict';
757 		// get this object
758 		var self = this;
759 		(function($) {
760 			// get array element container selector
761 			var selectorArrayElementContainer = $(self.getHTMLSelectorArrayElementContainer(projectId, formId, fieldId, number));
762 			// get previous and next numbers
763 			var prevNumber = selectorArrayElementContainer.data('prev');
764 			var nextNumber = selectorArrayElementContainer.data('next');
765 			// get array identifier
766 			var arrayId = $(self.getHTMLSelectorArrayContainer(projectId, formId, fieldId)).data('array-id');
767 			// update previous element
768 			if (prevNumber !== '') {
769 				var selectorArrayElementContainerPrev = $(self.getHTMLSelectorArrayElementContainer(projectId, formId, fieldId, prevNumber));
770 				self._updateControls(projectId, arrayId, prevNumber, false, '', true, nextNumber);
771 			}
772 			// update next element
773 			if (nextNumber !== '') {
774 				var selectorArrayElementContainerNext = $(self.getHTMLSelectorArrayElementContainer(projectId, formId, fieldId, nextNumber));
775 				self._updateControls(projectId, arrayId, nextNumber, true, prevNumber, false, '');
776 			}
777 			// delete element
778 			selectorArrayElementContainer.remove();
779 		}(jQuery));
780 	},
781 
782 	/**
783 	 * Add empty field
784 	 *
785 	 * @private
786 	 * @param {string} projectId Project identifier
787 	 * @param {string} formId Form identifier
788 	 * @param {string} fieldId Field identifier
789 	 * @return {void}
790 	 */
791 	_addNewEmptyField : function(projectId, formId, fieldId) {
792 		'use strict';
793 		// get this object
794 		var self = this;
795 		(function($) {
796 			// get array container selector
797 			var selectorArrayContainer = $(self.getHTMLSelectorArrayContainer(projectId, formId, fieldId));
798 			// get array identifier
799 			var arrayId = $(self.getHTMLSelectorArrayContainer(projectId, formId, fieldId)).data('array-id');
800 			// get array data
801 			var arrayData = self._prj['prj_' + projectId].arrays['arr_' + arrayId];
802 			// add new empty field
803 			selectorArrayContainer.append(self._valsDivElementContainer.replace('####DATA_TYPE_ELEMENT_CONTAINER####', 'element-container').replace('####DATA_ATTRS####', 'data-number="" data-prev="na" data-next="na"') + self._valsDivField.replace('####DATA_TYPE_FIELD####', 'field') + arrayData.emptyField + '</div>' + self._valsDivControls.replace('####DATA_TYPE_CONTROLS####', 'controls') + '</div><div style="clear:both;"></div></div>');
804 			// get empty array element container
805 			var selectorEmptyArrayElementContainer = selectorArrayContainer.find('div[data-type="element-container"]:last');
806 			// update array element number
807 			self._objDataHelper.setDataInDom(selectorEmptyArrayElementContainer, 'number', arrayData.newNumber);
808 			self._prj['prj_' + projectId].arrays['arr_' + arrayId].newNumber = arrayData.newNumber+1;
809 			// add adding controls to empty field after changing its value
810 			selectorEmptyArrayElementContainer.find('[data-type="field-value"]').bind('change.empty', {
811 				self          : self,
812 				projectId     : projectId,
813 				formId        : formId,
814 				fieldId       : fieldId,
815 				arrayId       : arrayId,
816 				arraySettings : arrayData.arraySettings
817 			}, function(event) {
818 				// disable default event
819 				event.preventDefault();
820 				// optionally add new empty field
821 				if ($(this).val() !== '') {
822 					// update last array element
823 					var previous = '';
824 					var selectorArrayElementContainer = selectorArrayContainer.find('div[data-type="element-container"][data-next=""]');
825 					if (selectorArrayElementContainer.length > 0) {
826 						previous = parseInt(selectorArrayElementContainer.data('number'), 10);
827 						var number = parseInt($(this).parent().parent().data('number'), 10);
828 						event.data.self._updateControls(event.data.projectId, event.data.arrayId, parseInt(selectorArrayElementContainer.data('number'), 10), false, '', true, number);
829 					}
830 					// update controls
831 					event.data.self._updateControls(event.data.projectId, event.data.arrayId, parseInt($(this).parent().parent().data('number'), 10), true, previous, true, '');
832 					// add new empty field
833 					if (event.data.arraySettings.autoadddeleteifempty) {
834 						event.data.self._addNewEmptyField(event.data.projectId, event.data.formId, event.data.fieldId);
835 					}
836 					// remove this event
837 					$(this).unbind('change.empty');
838 				}
839 			});
840 		}(jQuery));
841 	}
842 };
843 
844 // initialize
845 var kocujILV12aBackendSettingsFormArray = new kocujILV12aCBackendSettingsFormArray();
846