PageSourceSearch

https://admin.handheldcontact.com/remote/?action=resource-js&url=/functions/modules/pages3/js/fields-api.js

js handheldcontact.com collected 2026-10-02 00:00:55 UTC 76,290 bytes, 3,646 lines download raw bytes

1(function ()
2{
3	"use strict";
4
5	$.require(['touch'], function (touch)
6	{
7		var FIELDS_API = new FieldsAPI();
8		$.register('fields', FIELDS_API);
9
10		function getRandomNumber(fMax)
11		{
12			return Math.floor(Math.random() * (fMax + 1));
13		}
14
15		function FieldsAPI()
16		{
17			var THIS = this;
18
19			var FIELD_TYPE_MAP = {
20				text: TextField,
21				select: SelectField,
22				textarea: TextAreaField,
23				date: DateField,
24				datetime: DateTimeField,
25				checkbox: CheckBoxField,
26				hidden: HiddenField,
27				radio: SelectField,
28				password: PasswordField,
29				upload: UploadField,
30				label: LabelField,
31				phone: PhoneField,
32				selectmultiple: SelectMultipleField,
33				toggle: ToggleField,
34				//search: SearchField,
35				button: ButtonField,
36				//autocomplete: AutoCompleteField,
37				number: NumberField,
38				tinymce: TinyMceField,
39				radiolist: RadioListField,
40				checkboxlist: CheckBoxListField,
41				radiorow: RadioRowField,
42				checkboxrow: CheckBoxRowField,
43				color: ColorField,
44				confirmation: ConfirmationField,
45				signature: SignatureField,
46				range: RangeField,
47				textarray: TextArrayField,
48				email: EmailField,
49				url: UrlField
50			};
51
52			this.all = FIELD_TYPE_MAP;
53
54			this.get = get;
55			function get(fieldData)
56			{
57				if (typeof (FIELD_TYPE_MAP[fieldData.type]) === 'function')
58				{
59					return new FIELD_TYPE_MAP[fieldData.type](fieldData, THIS);
60				}
61
62				return new TextField(fieldData, THIS);
63			}
64
65			this.add = add;
66			function add(name, fieldDef)
67			{
68				if (typeof (FIELD_TYPE_MAP[name]) !== 'undefined')
69				{
70					console.warn('FieldsAPI', 'A definition with the name ' + name + ' has already been added.');
71					return;
72				}
73
74				FIELD_TYPE_MAP[name] = fieldDef;
75			}
76
77			this.override = override;
78			function override(name, fieldDef)
79			{
80				//console.log('FieldsAPI - field type override added', name);
81				FIELD_TYPE_MAP[name] = fieldDef;
82			}
83
84			this.getForm = getForm;
85			function getForm(data, onSubmit)
86			{
87				if (typeof (data.tabs) === 'object' && data.tabs.length > 0)
88				{
89					return new TabbedForm(data, onSubmit);
90				}
91				else
92				{
93					return new Form(data, onSubmit);
94				}
95
96			}
97
98			this.getTabbedForm = getTabbedForm;
99			function getTabbedForm(data, onSubmit)
100			{
101				return new TabbedForm(data, onSubmit);
102			}
103
104			var pluginsManager = new PluginsManager();
105			this.plugins = pluginsManager;
106
107			this.abstracts = {
108				field: TextField,
109				upload: UploadField
110			};
111		}
112
113		function PluginsManager()
114		{
115			var all = {};
116
117			this.add = add;
118			function add(name, plugin)
119			{
120				if (typeof (all[name]) === 'undefined')
121				{
122					all[name] = plugin;
123				}
124				else
125				{
126					console.warn('fields.pluginsManager', name, 'already exists');
127				}
128			}
129
130			this.get = get;
131			function get(name)
132			{
133				if (typeof (all[name]) === 'undefined')
134				{
135					console.warn('fields.pluginsManager', name, 'does not exist');
136					return null;
137				}
138
139				return all[name];
140			}
141
142
143			function start()
144			{
145
146			}
147
148			start();
149		}
150
151
152		function TextField(data)
153		{
154			var THIS = this;
155
156			this.variable = data.variable;
157
158			//overridable 
159			this.generateLabel = function ()
160			{
161				var $div = $('<div class="question" />').html(data.question);
162				if (typeof (data.description) !== 'undefined' && data.description !== null && data.description !== '')
163				{
164					var $description = $('<div class="description" />').html(data.description).appendTo($div);
165					$description.on('click', function ()
166					{
167						$(this).toggleClass('minimized');
168					});
169				}
170
171				return $div;
172			};
173
174
175			//overridable 
176			this.generateInput = function ()
177			{
178				return $('<input type="text" autocomplete="off" class="input-text" />').attr({
179					value: data.value,
180					placeholder: data.placeholder
181				});
182			};
183
184			this.createInput = createInput;
185			function createInput()
186			{
187				var input = THIS.generateInput().attr({
188					'data-variable': data.variable,
189					'data-index': data.index,
190					'data-group-index': data.groupIndex,
191					'data-type': data.type,
192					'data-value': data.value
193				}).addClass('do-input').addClass('input-' + data.type);
194
195				if (typeof (data.css) === 'object')
196				{
197					input.css(data.css);
198				}
199
200				if (typeof (data.style) === 'string')
201				{
202					input.attr('style', data.style);
203				}
204
205				if (typeof (data.index) === 'number')
206				{
207					input.attr('tabindex', data.index);
208				}
209
210				if (typeof (data.selectOnFocus) === 'boolean')
211				{
212					input.on('focus', function ()
213					{
214						input.select();
215					});
216				}
217				addFollowUpFieldsToReveal();
218				return input;
219			}
220
221
222			this.$label = null;
223			this.$input = null;
224			this.$container = null;
225
226			this.addTo = function ($modal)
227			{
228				THIS.$container = $('<div class="do-input-container" />').addClass('do-input-container-' + data.type).attr({
229					'data-variable': data.variable,
230					'data-columns': data.columns,
231					'data-hidden': isHiddenByDefault,
232					'data-required': data.required
233				}).appendTo($modal);
234
235
236				THIS.$label = THIS.generateLabel();
237				THIS.$input = THIS.createInput();
238
239				THIS.$label.appendTo(THIS.$container);
240				if (THIS.placeInputWithinLabel)
241				{
242					THIS.$input.appendTo(THIS.$label);
243				}
244				else
245				{
246					THIS.$input.appendTo(THIS.$container);
247				}
248
249
250				THIS.addListenersTo(THIS.$input);
251				THIS.onDisplay(THIS.$input, THIS.$label, THIS.$container);
252				return THIS.$container;
253			};
254
255			this.placeInputWithinLabel = false;
256
257
258			this.addListenersTo = function ($i)
259			{
260
261				//set your default listeners here
262				$i.on('keyup', function ()
263				{
264					THIS.onChange();
265				});
266
267				$i.on('paste', function ()
268				{
269					THIS.onChange();
270				});
271
272				$i.on('change', function ()
273				{
274					THIS.onChange();
275				});
276
277				$i.on('focus', function ()
278				{
279					THIS.onFocus();
280				});
281
282				$i.on('blur', function ()
283				{
284					THIS.onBlur();
285				});
286			};
287
288			this.triggerChanged = triggerChanged;
289			function triggerChanged()
290			{
291				THIS.$input.trigger($.Event('fieldValueChanged'), [THIS]);
292			}
293
294			this.getValue = function ()
295			{
296				return THIS.$input.val();
297			}
298
299
300			this.onChange = function ()
301			{
302				THIS.triggerChanged();
303			};
304
305			this.onFocus = function ()
306			{
307
308			};
309
310			this.onBlur = function ()
311			{
312
313			};
314
315			this.validate = function ()
316			{
317				if (!data.required)
318				{
319					return true;
320				}
321
322				if (isHiddenByDefault && !THIS.isRevealed)
323				{
324					return true;
325				}
326
327				if (THIS.isHidden === true)
328				{
329					return true;
330				}
331
332				return THIS.getValue() !== '';
333			};
334
335			this.onDisplay = function ($i, $label, $container)
336			{
337
338			};
339
340			this.onTabFocus = function () { };
341
342			this.addWarning = addWarning;
343			function addWarning()
344			{
345				//console.log(data.variable, 'addWarning');
346				THIS.$container.addClass('warning');
347			}
348
349			this.removeWarning = removeWarning;
350			function removeWarning()
351			{
352				//console.log(data.variable, 'removeWarning');
353				THIS.$container.removeClass('warning');
354			}
355
356			this.isRevealed = true;
357			var isHiddenByDefault = false;
358			if (typeof (data.ishidden) === 'boolean')
359			{
360				isHiddenByDefault = data.ishidden;
361				this.isRevealed = false;
362			}
363
364			this.reveal = reveal;
365			function reveal()
366			{
367				if (isHiddenByDefault === true)
368				{
369					THIS.$container.attr('data-revealed', true);
370					THIS.isRevealed = true;
371				}
372
373			}
374
375			this.unReveal = unReveal;
376			function unReveal()
377			{
378				//console.log(data.variable, 'f.unReveal, isHiddenByDefault', isHiddenByDefault);
379				if (isHiddenByDefault === true)
380				{
381					THIS.$container.attr('data-revealed', false);
382					THIS.isRevealed = false;
383
384				}
385			}
386
387			this.getFieldsToHide = getFieldsToHide;
388			function getFieldsToHide()
389			{
390				return [];
391			}
392
393			this.isHidden = false;
394			this.hide = hide;
395			function hide()
396			{
397				THIS.$container.attr('data-hide', true);
398				THIS.isHidden = true;
399			}
400
401			this.unHide = unHide;
402			function unHide()
403			{
404				THIS.$container.attr('data-hide', false);
405				THIS.isHidden = false;
406			}
407
408			this.receiveExpectedValue = receiveExpectedValue;
409			function receiveExpectedValue(key, value)
410			{
411			}
412
413			this.getExpectedValues = getExpectedValues;
414			function getExpectedValues()
415			{
416				if (typeof (data.expectedValues) === 'undefined')
417				{
418					return null;
419				}
420
421				return data.expectedValues;
422			}
423
424			this.getFollowUpInput = function ()
425			{
426				return null;
427			}
428
429
430			var followUps = new FollowUpFields(this);
431			this.followUps = followUps;
432
433			this.getExtraValues = function ()
434			{
435				return {};
436			};
437
438			this.setRequired = function (isRequired)
439			{
440				data.required = isRequired;
441				if (THIS.$container !== null)
442				{
443					THIS.$container.attr('data-required', isRequired);
444				}
445				
446			};
447
448			function addFollowUpFieldsToReveal()
449			{
450				if (typeof (data.options) === 'undefined')
451				{
452					return;
453				}
454
455				if (data.options === null)
456				{
457					return;
458				}
459
460				data.options.forEach(function (option)
461				{
462					if (typeof (option.fieldsToReveal) !== 'undefined' && option.fieldsToReveal !== null)
463					{
464						option.fieldsToReveal.forEach(function (fieldVariable)
465						{
466							THIS.followUps.addFieldToReveal(option.value, fieldVariable);
467							//console.log('checkbox', 'reveal: ' + fieldVariable);
468						});
469					}
470				});
471			}
472
473		}
474
475		function FollowUpFields(field)
476		{
477			var THIS = this;
478			/*
479			toReveal = {
480				fieldValue1: ['fieldVariable1', 'fieldVariable2', 'fieldVariable3'],
481				fieldValue2: ['fieldVariable4', 'fieldVariable5', 'fieldVariable6']
482			}
483			*/
484			var toReveal = {};
485			//this.toReveal = toReveal;
486
487			this.getFieldsToReveal = getFieldsToReveal;
488			function getFieldsToReveal(currentValue)
489			{
490				//console.log('getFieldsToReveal', toReveal);
491				if (typeof (toReveal[currentValue]) === 'undefined')
492				{
493					//console.log('getFieldsToReveal.' + currentValue, []);
494					return [];
495				}
496
497				//console.log('getFieldsToReveal.' + currentValue, toReveal[currentValue]);
498				return toReveal[currentValue];
499			}
500
501			this.addFieldToReveal = addFieldToReveal;
502			function addFieldToReveal(optionValue, fieldVariable)
503			{
504				if (typeof (toReveal[optionValue]) === 'undefined')
505				{
506					toReveal[optionValue] = [];
507				}
508
509				toReveal[optionValue].push(fieldVariable);
510				//console.log('addFieldToReveal', optionValue, fieldVariable);
511			}
512
513			var toInsert = {};
514			this.getFieldsToInsert = getFieldsToInsert;
515			function getFieldsToInsert()
516			{
517				var currentValue = field.getValue();
518				//console.log(field.variable, 'currentValue', currentValue);
519
520				if (typeof (toInsert[currentValue]) === 'undefined')
521				{
522					//console.log(field.variable, 'getFieldsToInsert', toInsert);
523					return [];
524				}
525
526				return toInsert[currentValue];
527
528			}
529
530			this.addFieldToInsert = addFieldToInsert;
531			function addFieldToInsert(optionValue, fieldData)
532			{
533				if (typeof (toInsert[optionValue]) === 'undefined')
534				{
535					toInsert[optionValue] = [];
536				}
537
538				toInsert[optionValue].push(fieldData);
539			}
540
541			this.insertFieldsToInsert = insertFieldsToInsert;
542			function insertFieldsToInsert($fieldContainer)
543			{
544				removeInsertedFields($fieldContainer);
545				var currentValue = field.getValue();
546				var fieldsToInsert = getFieldsToInsert(currentValue);
547				if (fieldsToInsert.length === 0)
548				{
549					return;
550				}
551
552
553			}
554
555			function removeInsertedFields($fieldContainer)
556			{
557				$fieldContainer.parent().find('[data-inserted-by="' + field.variable + '"]').remove();
558
559			}
560
561
562			function start()
563			{
564
565			}
566
567			start();
568
569		}
570
571		function TextAreaField(data)
572		{
573			//lets override TextField so we don't need to repeat any code
574			TextField.call(this, data);
575			var THIS = this;
576
577			//override the base TextField.generateInput method
578			this.generateInput = function ()
579			{
580				var $item = $('<textarea autocomplete="off" />').attr({
581					placeholder: data.placeholder
582				}).val(data.value);
583
584				if (typeof (data.height) === 'number' && data.height > 0)
585				{
586					$item.css('height', data.height);
587				}
588
589				return $item;
590			}
591		}
592
593		function SelectField(data)
594		{
595			TextField.call(this, data);
596			var THIS = this;
597
598			this.generateInput = function ()
599			{
600				var $select = $('<select />').attr({
601					placeholder: data.placeholder,
602					'data-variable': data.variable,
603				});
604
605				if (data.type === 'selectmultiple')
606				{
607					$select.attr('multiple', true).addClass('input-select-multiple');
608				}
609				getOption({
610					value: '',
611					label: '-- Select One --'
612				}).appendTo($select);
613
614				data.options.forEach(function (option)
615				{
616					getOption(option).appendTo($select);
617					//addFieldsToReveal(option);
618				});
619
620				return $select;
621			}
622
623			this.addListenersTo = function ($input)
624			{
625				$input.on('change', function ()
626				{
627					THIS.onChange();
628				});
629			};
630
631
632			function getOption(optionData)
633			{
634				var $option = $('<option />').attr({
635					value: optionData.value
636				}).html(optionData.label);
637
638				if (optionData.value === data.value)
639				{
640					$option.attr('selected', true);
641				}
642
643				return $option;
644			}
645
646			this.addOption = addOption;
647			function addOption(label, value)
648			{
649				return $('<option />').attr('value', value).html(label).appendTo(THIS.$input);
650			}
651
652			
653			function addFieldsToReveal(option)
654			{
655				if (typeof (option.fieldsToReveal) === 'undefined')
656				{
657					return;
658				}
659
660				if (option.fieldsToReveal === null)
661				{
662					return;
663				}
664
665				option.fieldsToReveal.forEach(function (fieldVariable)
666				{
667					//console.log('addFieldsToReveal', fieldVariable);
668					THIS.followUps.addFieldToReveal(option.value, fieldVariable);
669				});
670			}
671			
672			this.getFieldsToHide = function ()
673			{
674				if (typeof (data.fieldsToHideOnSelect) === 'undefined')
675				{
676					return [];
677				}
678
679				if (data.fieldsToHideOnSelect === null)
680				{
681					return [];
682				}
683
684				var currentValue = THIS.getValue();
685				if (currentValue === '')
686				{
687					return [];
688				}
689
690				return data.fieldsToHideOnSelect;
691			};
692
693			this.setOptions = setOptions;
694			function setOptions(options)
695			{
696				data.options = options;
697				THIS.$input.html('');
698				data.options.forEach(function (option)
699				{
700					getOption(option).appendTo(THIS.$input);
701					addFieldsToReveal(option);
702				});
703
704			}
705
706			function onChange()
707			{
708				THIS.onChange();
709				setSubFields();
710			}
711
712			function setSubFields()
713			{
714				var option = findOption(THIS.getValue());
715				if (option === null)
716				{
717					return;
718				}
719
720				if (typeof (option.subFields) === 'undefined')
721				{
722					return;
723				}
724
725				if (option.subFields === 'null')
726				{
727					return;
728				}
729
730
731			}
732
733			function findOption(value)
734			{
735				var result = null;
736				data.options.forEach(function (option)
737				{
738					if (result === null && option.value === value)
739					{
740						result = option;
741					}
742				});
743				return result;
744			}
745
746			this.setValue = setValue;
747			function setValue(value)
748			{
749				THIS.$input.val(value);
750			}
751		}
752
753		function DateField(data)
754		{
755			TextField.call(this, data);
756			var THIS = this;
757
758			this.generateInput = function ()
759			{
760				if (isValidDateValue())
761				{
762					return $('<input type="date" autocomplete="off" class="input-text" />').attr({
763						value: data.value,
764						placeholder: data.placeholder
765					});
766				}
767				else
768				{
769					return generateLegacyDateInput();
770
771				}
772			}
773
774			function isValidDateValue()
775			{
776				if (data.value === null || data.value === '')
777				{
778					return true;
779				}
780
781				return data.value.indexOf('-') > -1;
782			}
783
784			var isLegacy = false;
785			function generateLegacyDateInput()
786			{
787				isLegacy = true;
788				return $('<input type="text" class="input-text input-date" />').attr({
789					placeholder: data.placeholder,
790					value: data.value
791				});
792			}
793
794			this.onDisplay = function ($input)
795			{
796				if (!isLegacy)
797				{
798					return;
799				}
800
801				if (typeof ($.datepicker) === 'object')
802				{
803					$input.datepicker();
804				}
805
806			};
807		}
808
809		function DateTimeField(data)
810		{
811			TextField.call(this, data);
812			var THIS = this;
813
814			this.generateInput = function ()
815			{
816				if (isValidDateValue())
817				{
818					return $('<input type="datetime-local" autocomplete="off" />').attr({
819						value: data.value,
820						placeholder: data.placeholder
821					});
822				}
823				else
824				{
825					return generateLegacyDateInput();
826
827				}
828
829			}
830
831			var isLegacy = false;
832			function generateLegacyDateInput()
833			{
834				isLegacy = true;
835				return $('<input type="text" autocomplete="off" class="input-text input-datetime" />').attr({
836					placeholder: data.placeholder,
837					value: data.value
838				});
839			}
840
841			function isValidDateValue()
842			{
843				if (data.value === null || data.value === '')
844				{
845					return true;
846				}
847
848				return data.value.indexOf('-') > -1;
849			}
850
851			this.onDisplay = function ($input)
852			{
853				if (!isLegacy)
854				{
855					return;
856				}
857
858				if (typeof ($.fn.datetimepicker) !== 'function')
859				{
860					console.warn('do.DateTimeField', 'jquery.datetimepicker is not installed');
861					return;
862				}
863
864				$input.datetimepicker({
865					controlType: 'select',
866					oneLine: true,
867					onClose: function ()
868					{
869						$(this).trigger('change');
870					},
871					timeFormat: 'h:mm tt',
872					ampm: true
873				});
874
875				$input.attr('autocomplete', 'off');
876			};
877		}
878
879
880		function CheckBoxField(data)
881		{
882			TextField.call(this, data);
883			var THIS = this;
884			var label = getCheckboxLabel();
885			var hasLabel = label !== null;
886			this.hasLabel = hasLabel;
887
888			if (!hasLabel)
889			{
890				this.placeInputWithinLabel = true;
891			}
892
893			function getCheckboxLabel()
894			{
895				if (typeof (data.checkboxLabel) === 'undefined' || data.checkboxLabel === null)
896				{
897					return null;
898				}
899
900				if (data.checkboxLabel === '')
901				{
902					return null;
903				}
904
905				return data.checkboxLabel;
906			}
907
908			this.generateLabel = function ()
909			{
910				var $div = $('<div class="question" />').attr('data-has-label', hasLabel);
911				$('<div class="question-inner" />').html(data.question).appendTo($div);
912				return $div;
913			};
914
915			this.generateInput = function ()
916			{
917				if (hasLabel)
918				{
919					return generateInputWithLabel();
920				}
921				else
922				{
923					return getCheckbox();
924				}
925			}
926
927			function getCheckbox()
928			{
929				var $input = $('<input type="checkbox" />');
930				if (data.value === '1')
931				{
932					$input.attr('checked', true);
933					$input.prop('checked', true);
934				}
935				return $input;
936			}
937
938			function generateInputWithLabel()
939			{
940				var $div = $('<div class="checkbox-container" />');
941				getCheckbox().appendTo($div);
942				$('<div class="checkbox-label" />').html(label).appendTo($div);
943				return $div;
944			}
945			this.onDisplay = function ($input, $label)
946			{
947				if (hasLabel)
948				{
949					$input.on('click', 'input', function (e)
950					{
951						THIS.onCheckboxClick();
952						e.stopPropagation();
953					});
954
955					$input.on('click', function (e)
956					{
957						$input.find('input').trigger('click');
958					});
959
960					$label.on('click', function (e)
961					{
962						$input.find('input').trigger('click');
963					});
964				}
965				else
966				{
967					$input.on('click', function (e)
968					{
969						THIS.onCheckboxClick();
970						e.stopPropagation();
971					});
972
973					$label.on('click', function (e)
974					{
975						$input.trigger('click');
976					});
977				}
978
979
980			};
981
982			this.onCheckboxClick = function ()
983			{
984			};
985
986			this.validate = function ()
987			{
988				return true;
989			};
990
991			this.getValue = function ()
992			{
993				if (hasLabel)
994				{
995					if (THIS.$input.find('input').prop('checked') === true)
996					{
997						return '1';
998					}
999					else
1000					{
1001						return '0';
1002					}
1003
1004				}
1005				else
1006				{
1007					if (THIS.$input.prop('checked') === true)
1008					{
1009						return '1';
1010					}
1011					else
1012					{
1013						return '0';
1014					}
1015				}
1016			}
1017
1018
1019		}
1020
1021		function HiddenField(data)
1022		{
1023			TextField.call(this, data);
1024			var THIS = this;
1025			this.generateInput = function ()
1026			{
1027				return $('<input type="hidden" />').attr('value', data.value);
1028			}
1029
1030			this.validate = function ()
1031			{
1032				return true;
1033			};
1034
1035			this.onDisplay = function ($input, $label, $container)
1036			{
1037				$container.hide();
1038			};
1039
1040			this.getValue = function ()
1041			{
1042				if (data.value !== null)
1043				{
1044					return data.value;
1045				}
1046
1047				return '';
1048			}
1049		}
1050
1051		function PasswordField(data)
1052		{
1053			TextField.call(this, data);
1054			var THIS = this;
1055			this.generateInput = function ()
1056			{
1057				return $('<input type="password" autocomplete="new-password" />').attr({
1058					placeholder: data.placeholder,
1059					value: data.value
1060				});
1061			}
1062
1063		}
1064		function EmailField(data)
1065		{
1066			TextField.call(this, data);
1067			var THIS = this;
1068			this.generateInput = function ()
1069			{
1070				return $('<input type="email" autocomplete="off" />').attr({
1071					placeholder: data.placeholder,
1072					value: data.value
1073				});
1074			}
1075
1076			var _$input = null;
1077			this.onDisplay = function ($input, $question, $container)
1078			{
1079				_$input = $input;
1080			};
1081
1082			this.validate = function ()
1083			{
1084				var v = THIS.getValue();
1085
1086				if (v === '')
1087				{
1088					if (data.required)
1089					{
1090						return false;
1091					}
1092					else
1093					{
1094						return true;
1095					}
1096
1097				}
1098
1099				if (THIS.isHidden === true)
1100				{
1101					return true;
1102				}
1103
1104				return _$input[0].validity.valid;
1105			};
1106
1107
1108		}
1109
1110		function UrlField(data)
1111		{
1112			TextField.call(this, data);
1113			var THIS = this;
1114			this.generateInput = function ()
1115			{
1116				return $('<input type="url" autocomplete="off" />').attr({
1117					placeholder: data.placeholder,
1118					value: data.value
1119				});
1120			}
1121
1122		}
1123
1124		function RangeField(data)
1125		{
1126			TextField.call(this, data);
1127
1128			var $input = null;
1129			this.generateInput = function ()
1130			{
1131				$input = $('<input type="range" />').attr({
1132					value: data.value
1133				});
1134
1135				if (typeof (data.min) !== 'undefined')
1136				{
1137					$input.attr('min', data.min);
1138				}
1139
1140				if (typeof (data.max) !== 'undefined')
1141				{
1142					$input.attr('max', data.max);
1143				}
1144
1145				if (typeof (data.step) !== 'undefined')
1146				{
1147					$input.attr('step', data.step);
1148				}
1149
1150				return $input;
1151			}
1152
1153			var $valueDisplay = null;
1154			this.onDisplay = function ($i, $label, $container)
1155			{
1156				$valueDisplay = $('<div class="value-display" />').appendTo($container);
1157
1158				$valueDisplay.html($i.val());
1159				$i.on('input', function ()
1160				{
1161					$valueDisplay.html($i.val());
1162				});
1163
1164			};
1165
1166			this.setValue = function (v)
1167			{
1168				$input.val(v);
1169				$valueDisplay.html(v);
1170			};
1171		}
1172
1173		function UploadField(options)
1174		{
1175			TextField.call(this, options);
1176			var THIS = this;
1177
1178			var uploadResult = null;
1179
1180			this.generateInput = function ()
1181			{
1182				/*
1183					if (typeof ($.fn.fileupload) !== 'function')
1184					{
1185						return getUploadUnavailable('FileUpload plugin is not present');
1186					}
1187					*/
1188				if (typeof (options.action) === 'undefined' || options.action === '')
1189				{
1190					options.action = 'do-upload';
1191				}
1192
1193				var div = $('<div class="input-file" style="border: 0 none transparent; padding: 15px 0;" />');
1194				var progressOuter = $('<div class="progress" />').appendTo(div);
1195				//var progressBar = $('<div class="progress-bar" style="display: none; border-radius: 2px; background-color: #00FF00; width: 0%; min-height: 20px; text-align: center; color: #FFF; pointer-events: none;" />').appendTo(progressOuter);
1196				var progressBar = $('<div class="progress-bar" />').appendTo(progressOuter);
1197				var progressBarLabel = $('<div class="progress-bar-label" />').appendTo(progressOuter);
1198				var $field = $('<input type="file" />').appendTo(div);
1199				if (options.isMultiple)
1200				{
1201					$field.attr('multiple', true);
1202				}
1203
1204				return div;
1205			}
1206
1207			function getUploadUnavailable(reason)
1208			{
1209				return $('<div />').html('<em>ERROR: Uploader is unavailable because ' + reason + '</em>');
1210			}
1211
1212			this.url = function ()
1213			{
1214				console.warn('This upload field [url] must be overridden.');
1215				return '';
1216			};
1217			/*
1218			function _getUploadUrl()
1219			{
1220				return req.url + options.action + '&variable=' + options.variable + req.varsWithInputs();
1221			}
1222			*/
1223			this.onFinished = function (data)
1224			{
1225				//console.warn('This upload field [onFinished] must be overridden.');
1226			};
1227
1228			this.onDisplay = function (div, $label)
1229			{
1230				var $field = div.find('input');
1231				var progressOuter = div.find('.progress').attr('data-status', 'none');
1232
1233				var progressBar = div.find('.progress-bar');
1234				var progressBarLabel = div.find('.progress-bar-label');
1235
1236				ensureInstalled(function ()
1237				{
1238					$field.fileupload({
1239						//returns the function serialized :( url: _getUploadUrl,
1240
1241						url: THIS.url() + options.action + '&variable=' + options.variable,
1242						dataType: 'json',
1243						progressInterval: 1,
1244						bitrateInterval: 10,
1245						dropZone: null,
1246						done: function (e, data)
1247						{
1248
1249							if (typeof (data.result.value) === 'string')
1250							{
1251								uploadResult = data.result.value;
1252							}
1253							else
1254							{
1255								uploadResult = '';
1256							}
1257
1258							div.find('input').css('display', 'none');
1259							if (data.result.status === 'OK' || data.result.status === 'MESSAGE')
1260							{
1261								progressOuter.attr('data-status', 'completed');
1262								progressBarLabel.html('<i class="fa fa-check-circle-o" /><span>File Uploaded</span>');
1263
1264								/*
1265								progressBar.html('<i class="fa fa-check-circle-o" /><span>File Uploaded</span>').css({
1266									'background-color': 'transparent',
1267									'color': '#000000'
1268								});
1269								*/
1270
1271								THIS.onFinished(data);
1272							}
1273							else
1274							{
1275								progressOuter.attr('data-status', 'error');
1276								progressBarLabel.html('<i class="fa fa-times-circle-o" /><span>' + data.result.message + '</span>');
1277
1278								/*
1279								progressBar.html('<i class="fa fa-times-circle-o" /><span>' + data.result.message + '</span>').css({
1280									'background-color': '#FF0000',
1281									'color': '#FFFFFF',
1282									'height': 'auto'
1283								});
1284								*/
1285
1286								div.find('input').css('display', 'block');
1287
1288							}
1289						},
1290						progressall: function (e, data)
1291						{
1292							div.find('input').css('display', 'none');
1293
1294							var progress = parseInt(data.loaded / data.total * 100, 10);
1295							//div.data('progress', progress);
1296
1297							progressOuter.attr('data-progress', progress);
1298
1299							if (progress === 100)
1300							{
1301								progressBarLabel.html(progress + '% Processing...');
1302								progressOuter.attr('data-status', 'processing');
1303
1304								/*
1305
1306								progressBar.css({
1307									'display': 'block',
1308									'width': progress + '%',
1309									'background-color': '#00FF00'
1310								}).html('Processing... Please Wait...');
1311								*/
1312
1313							}
1314							else
1315							{
1316								progressBarLabel.html(progress + '% Uploading File...');
1317								progressOuter.attr('data-status', 'uploading');
1318								/*
1319											progressBar.css({
1320												'display': 'block',
1321												'width': progress + '%',
1322												'background-color': '#00FF00'
1323											}).html(progress + '% Uploading File...');
1324											*/
1325
1326							}
1327						},
1328						add: function (e, data)
1329						{
1330							//console.log('upload', 'add');
1331							data.url = THIS.url() + options.action + '&variable=' + options.variable;//_getUploadUrl();
1332							//console.log('upload', data.url);
1333							data.submit();
1334						}
1335					});
1336				});
1337
1338
1339			}
1340
1341			var isInstalled = typeof ($.fn.fileupload) !== 'undefined';
1342
1343			function ensureInstalled(callback)
1344			{
1345				if (isInstalled)
1346				{
1347					callback();
1348					return;
1349				}
1350
1351				if (options.javascriptResources === null)
1352				{
1353					options.javascriptResources = ['/remote/?action=resource-js&url=/functions/modules/pages3/js/jquery.fileupload.js'];
1354				}
1355				//console.log('options', options);
1356				$.require(['scriptInstaller'], function (scriptInstaller)
1357				{
1358					scriptInstaller.install(options.javascriptResources, function ()
1359					{
1360						isInstalled = true;
1361						callback();
1362					});
1363				});
1364
1365			}
1366			this.getValue = function ()
1367			{
1368				return uploadResult;
1369			}
1370
1371			this.validate = function ()
1372			{
1373				if (!options.required)
1374				{
1375					return true;
1376				}
1377
1378				if (!THIS.isRevealed)
1379				{
1380					return true;
1381				}
1382
1383				if (THIS.isHidden)
1384				{
1385					return true;
1386				}
1387
1388				return uploadResult !== null;
1389			}
1390
1391
1392		}
1393
1394
1395		function LabelField(data)
1396		{
1397			TextField.call(this, data);
1398			var THIS = this;
1399
1400			this.generateInput = function ()
1401			{
1402				return $('<div />');
1403			};
1404
1405			this.validate = function ()
1406			{
1407				return true;
1408			};
1409
1410			this.getValue = function ()
1411			{
1412				if (data.value !== null)
1413				{
1414					return data.value;
1415				}
1416
1417				return '';
1418			};
1419		}
1420
1421		function PhoneField(data)
1422		{
1423			TextField.call(this, data);
1424			var THIS = this;
1425			this.generateInput = function ()
1426			{
1427				return $('<input type="tel" autocomplete="off" class="input-text input-phone" />').attr({
1428					placeholder: data.placeholder,
1429					value: data.value
1430				});
1431			}
1432		}
1433		function SelectMultipleField(data)
1434		{
1435			SelectField.call(this, data);
1436			var THIS = this;
1437		}
1438		function ToggleField(data)
1439		{
1440			CheckBoxField.call(this, data);
1441			var THIS = this;
1442		}
1443
1444		function ConfirmationField(data)
1445		{
1446			CheckBoxField.call(this, data);
1447			var THIS = this;
1448
1449			this.onCheckboxClick = function ()
1450			{
1451				//THIS.$input.trigger($.Event('change'));
1452
1453				THIS.triggerChanged();
1454			};
1455
1456			this.validate = function ()
1457			{
1458				if (THIS.hasLabel)
1459				{
1460					return THIS.$input.find('input').prop('checked') === true;
1461				}
1462				else
1463				{
1464					return THIS.$input.prop('checked') === true;
1465				}
1466			};
1467		}
1468
1469
1470		function SearchField(data)
1471		{
1472			TextField.call(this, data);
1473			var THIS = this;
1474		}
1475
1476		function ButtonField(data)
1477		{
1478			TextField.call(this, data);
1479			var THIS = this;
1480
1481			this.generateInput = function ()
1482			{
1483				var button = $('<a class="button" />');
1484
1485				if (data.icon !== '')
1486				{
1487					$('<i class="fa fa-' + data.icon + '" />').appendTo(button);
1488				}
1489				$('<span />').html(data.question).appendTo(button);
1490
1491
1492				return button;
1493			};
1494
1495			this.onDisplay = function ($input, $label, $container)
1496			{
1497				$input.on('click', function ()
1498				{
1499					onClick($(this));
1500				});
1501			};
1502
1503			function onClick($button)
1504			{
1505				if (typeof (data.onClick) === 'function')
1506				{
1507					//req.close();
1508					data.onClick($button);
1509					return;
1510				}
1511				var o = {
1512					action: data.action
1513				};
1514
1515				if (typeof (data.id) === 'number')
1516				{
1517					o.id = data.id;
1518				}
1519
1520				if (typeof (data.vars) === 'object')
1521				{
1522					o.vars = data.vars;
1523				}
1524
1525				if (typeof (data.post) === 'object')
1526				{
1527					o.post = data.post;
1528				}
1529				var r = $.extend(true, req.options, o);
1530				//req.close();
1531				//req.doPlugin.run(r);
1532			}
1533
1534			this.getValue = function ()
1535			{
1536				return '';
1537			}
1538
1539			this.validate = function ()
1540			{
1541				return true;
1542			};
1543
1544		}
1545
1546		function AutoCompleteField(data)
1547		{
1548			TextField.call(this, data);
1549			var THIS = this;
1550
1551			this.generateInput = function ()
1552			{
1553				var field = $('<input type="text" autocomplete="off" class="input-text input-autocomplete" />');
1554				var v = '';
1555				data.options.forEach(function (option)
1556				{
1557					if (option.value == data.value)
1558					{
1559						v = option.label;
1560					}
1561				});
1562
1563				field.attr('value', v).attr('data-selected-value', data.value);
1564				return field;
1565
1566			};
1567
1568			this.onDisplay = function ($input, $label, $container)
1569			{
1570				$container.addClass('ui-front');
1571				$input.autocomplete({
1572					source: data.options,
1573					open: function (e, ui)
1574					{
1575						$container.addClass('focused');
1576					},
1577					close: function (e, ui)
1578					{
1579						$container.removeClass('focused');
1580					},
1581					select: function (e, ui)
1582					{
1583						$input.attr('data-selected-value', ui.item.value);
1584						$input.val(ui.item.label);
1585						return false;
1586					},
1587					autoFocus: true
1588				});
1589				$input.attr('data-selected-value', data.value);
1590
1591			};
1592
1593			this.getValue = function ()
1594			{
1595				return THIS.$input.attr('data-selected-value');
1596			};
1597
1598			this.validate = function ()
1599			{
1600				return true;
1601			};
1602		}
1603
1604		function NumberField(data)
1605		{
1606			TextField.call(this, data);
1607			var THIS = this;
1608			this.generateInput = function ()
1609			{
1610				var $input = $('<input type="number" autocomplete="off" class="input-text" />').attr({
1611					value: data.value,
1612					placeholder: data.placeholder
1613				});
1614
1615				if (typeof (data.step) === 'number')
1616				{
1617					$input.attr('step', data.step);
1618				}
1619
1620				if (typeof (data.min) === 'number')
1621				{
1622					$input.attr('min', data.min);
1623				}
1624
1625				if (typeof (data.max) === 'number')
1626				{
1627					$input.attr('max', data.max);
1628				}
1629
1630				return $input;
1631			};
1632		}
1633
1634
1635		var TINYMCE_INSTALLED = false;
1636		function TinyMceField(data)
1637		{
1638			TextField.call(this, data);
1639			var THIS = this;
1640
1641			var id = 'do_tinymce_' + getRandomNumber(999999);
1642
1643			this.generateInput = function ()
1644			{
1645				var $item = $('<textarea autocomplete="off" />').attr({
1646					placeholder: data.placeholder,
1647					id: id
1648				}).val(data.value);
1649
1650				if (typeof (data.height) === 'number')
1651				{
1652					$item.css('height', data.height);
1653				}
1654
1655				return $item;
1656			}
1657
1658			this.onDisplay = function ($input, $label, $container)
1659			{
1660				ensureInstalled(function ()
1661				{
1662					var options = $.extend(true, data.tinymceOptions, {
1663						selector: '#' + $input.attr('id'),
1664						content_css: '/remote/?action=do-tinymce-css',
1665					});
1666
1667					if (!touch.isTouch() && typeof (data.sendKeyUpEvents) === 'boolean' && data.sendKeyUpEvents === true)
1668					{
1669						options.init_instance_callback = function (ed)
1670						{
1671							ed.on('keyup', function (e)
1672							{
1673								$input.trigger($.Event('change'));
1674								$input.trigger($.Event('htmlchanged'));
1675							});
1676						};
1677					}
1678						
1679					//hack to get it to initialize in AF
1680					setTimeout(function ()
1681					{
1682						tinyMCE.init(options);
1683					}, 30);
1684				});
1685			};
1686
1687			this.getValue = function ()
1688			{
1689				if (typeof (tinyMCE) === 'undefined')
1690				{
1691					return data.value;
1692				}
1693
1694				var f = tinyMCE.get(id);
1695				if (f === null)
1696				{
1697					return data.value;
1698				}
1699
1700				return f.getContent();
1701			};
1702
1703			function ensureInstalled(callback)
1704			{
1705				if (TINYMCE_INSTALLED === true)
1706				{
1707					callback();
1708					return;
1709				}
1710
1711				if (typeof (window.tinyMCE) === 'object')
1712				{
1713					TINYMCE_INSTALLED = true;
1714					callback();
1715					return;
1716				}
1717
1718				$.require(['scriptInstaller'], function (scriptInstaller)
1719				{
1720					scriptInstaller.install(data.javascriptResources, function ()
1721					{
1722						TINYMCE_INSTALLED = true;
1723						callback();
1724					});
1725				});
1726
1727			}
1728		}
1729
1730
1731
1732		function RadioListField(data)
1733		{
1734			TextField.call(this, data);
1735			var THIS = this;
1736
1737			var id = 'do_radiolist_' + getRandomNumber(999999);
1738			this.getLayout = function ()
1739			{
1740				return 'column';
1741			};
1742
1743			this.generateInput = function ()
1744			{
1745				var $select = $('<div class="input-list" />').attr({
1746					placeholder: data.placeholder,
1747					'data-variable': data.variable,
1748					'data-layout': THIS.getLayout(),
1749					'data-option-columns': data.optionColumns
1750				});
1751
1752				data.options.forEach(function (option)
1753				{
1754					getOption(option).appendTo($select);
1755				});
1756
1757				return $select;
1758			}
1759
1760
1761			function getOption(optionData)
1762			{
1763				var $option = $('<div class="option" />').attr({
1764					'data-value': optionData.value
1765				});
1766
1767				var cbid = id + '_' + getRandomNumber(999999);
1768
1769				var $input = $('<input type="radio" />').attr({
1770					name: id,
1771					id: cbid,
1772					value: optionData.value
1773				}).appendTo($option);
1774
1775				if (optionData.value === data.value)
1776				{
1777					$input.prop('checked', true);
1778				}
1779
1780				var $label = $('<label />').attr({
1781					for: cbid
1782				}).html(optionData.label).appendTo($option);
1783
1784
1785				return $option;
1786
1787			}
1788
1789			//override the base TextField.addListenersTo method
1790			this.addListenersTo = function ($input)
1791			{
1792				$input.on('click', 'input', function ()
1793				{
1794					data.value = $(this).attr('value');
1795					//console.log('value set to ', data.value);
1796					THIS.onChange();
1797				});
1798			};
1799
1800			this.getValue = function ()
1801			{
1802				return data.value;
1803			};
1804
1805
1806		}
1807
1808		function RadioRowField(data)
1809		{
1810			RadioListField.call(this, data);
1811			this.getLayout = function ()
1812			{
1813				return 'row';
1814			};
1815		}
1816
1817
1818		function CheckBoxListField(data)
1819		{
1820			TextField.call(this, data);
1821			var THIS = this;
1822
1823			var id = 'do_checkboxlist_' + getRandomNumber(999999);
1824
1825			this.getLayout = function ()
1826			{
1827				return 'column';
1828			};
1829
1830			this.generateInput = function ()
1831			{
1832				var $select = $('<div class="input-list" />').attr({
1833					placeholder: data.placeholder,
1834					'data-variable': data.variable,
1835					'data-layout': THIS.getLayout(),
1836					'data-option-columns': data.optionColumns
1837				});
1838
1839				data.options.forEach(function (option)
1840				{
1841					getOption(option).appendTo($select);
1842				});
1843
1844				return $select;
1845			}
1846
1847			function getOption(optionData)
1848			{
1849				var $option = $('<div class="option" />').attr({
1850					'data-value': optionData.value
1851				});
1852
1853				var cbid = id + '_' + getRandomNumber(999999);
1854
1855				var $input = $('<input type="checkbox" />').attr({
1856					name: id,
1857					id: cbid,
1858					value: optionData.value
1859				}).appendTo($option);
1860
1861				if (optionData.value === data.value)
1862				{
1863					$input.prop('checked', true);
1864				}
1865
1866				var $label = $('<label />').attr({
1867					for: cbid
1868				}).html(optionData.label).appendTo($option);
1869
1870
1871				return $option;
1872
1873			}
1874
1875			//override the base TextField.addListenersTo method
1876			this.addListenersTo = function ($input)
1877			{
1878				$input.on('click', 'input', function ()
1879				{
1880					THIS.onChange();
1881				});
1882			};
1883
1884
1885			this.getValue = function ()
1886			{
1887				var values = [];
1888				this.$input.find('input').each(function ()
1889				{
1890					var cb = $(this);
1891					if (cb.prop('checked') === true)
1892					{
1893						values.push(cb.attr('value'));
1894					}
1895				});
1896				return JSON.stringify(values);
1897			};
1898
1899
1900			this.validate = function ()
1901			{
1902				return true;
1903			};
1904
1905
1906		}
1907
1908		function CheckBoxRowField(data)
1909		{
1910			CheckBoxListField.call(this, data);
1911			this.getLayout = function ()
1912			{
1913				return 'row';
1914			};
1915		}
1916
1917		function ColorField(data)
1918		{
1919			TextField.call(this, data);
1920
1921			this.generateInput = function ()
1922			{
1923				return $('<input type="color" class="input-text" />').attr({
1924					value: data.value,
1925					//colorpicker: 'hex',
1926					placeholder: data.placeholder
1927				});
1928			};
1929
1930		}
1931
1932		function SignatureField(data)
1933		{
1934			var THIS = this;
1935			var canvas = null;
1936			var ctx = null;
1937
1938			TextField.call(this, data);
1939
1940
1941			this.generateInput = function ()
1942			{
1943				var $div = $('<div class="signature-container" />');
1944				$('<canvas width="400" height="200" style="border: 1px dashed #CCC; cursor: pointer;" />').appendTo($div);
1945				getResetButton().appendTo($div);
1946
1947				return $div;
1948			};
1949
1950			this.onDisplay = function ($input, $label, $container)
1951			{
1952				$container.on('click', '[data-reset-signature-button]', function ()
1953				{
1954					reset();
1955				});
1956
1957				canvas = getCanvas()[0];
1958				ctx = canvas.getContext("2d");
1959
1960				canvas.addEventListener("mousemove", function (e)
1961				{
1962					if (isDrawing)
1963					{
1964						findxy('move', e);
1965					}
1966				}, false);
1967
1968				canvas.addEventListener("mousedown", function (e)
1969				{
1970					isDrawing = true;
1971					findxy('down', e);
1972				}, false);
1973
1974				canvas.addEventListener("mouseup", function (e)
1975				{
1976					if (isDrawing)
1977					{
1978						isDrawing = false;
1979						isDrawed = true;
1980						findxy('up', e);
1981					}
1982
1983				}, false);
1984
1985				canvas.addEventListener("mouseleave", function (e)
1986				{
1987					if (isDrawing)
1988					{
1989						findxy('out', e);
1990					}
1991				}, false);
1992
1993			};
1994
1995
1996			function getCanvas()
1997			{
1998				return THIS.$input.find('canvas');
1999			}
2000
2001
2002			var flag = false,
2003				prevX = 0,
2004				currX = 0,
2005				prevY = 0,
2006				currY = 0,
2007				dot_flag = false;
2008
2009			var x = "black",
2010				y = 2;
2011
2012
2013			var isDrawing = false;
2014			var isDrawed = false;
2015
2016
2017			function getResetButton()
2018			{
2019				var div = $('<div />');
2020				$('<a class="button" data-reset-signature-button />').html('Clear').appendTo(div);
2021				return div;
2022			}
2023
2024			function reset()
2025			{
2026				isDrawed = false;
2027				isDrawing = false;
2028				THIS.$input.find('canvas').remove();
2029				$('<canvas width="400" height="200" style="border: 1px dashed #CCC; cursor: pointer;" />').prependTo(THIS.$input);
2030				canvas = getCanvas()[0];
2031				ctx = canvas.getContext("2d");
2032
2033				canvas.addEventListener("mousemove", function (e)
2034				{
2035					if (isDrawing)
2036					{
2037						findxy('move', e);
2038					}
2039				}, false);
2040
2041				canvas.addEventListener("mousedown", function (e)
2042				{
2043					isDrawing = true;
2044					findxy('down', e);
2045				}, false);
2046
2047				canvas.addEventListener("mouseup", function (e)
2048				{
2049					if (isDrawing)
2050					{
2051						isDrawing = false;
2052						isDrawed = true;
2053						findxy('up', e);
2054					}
2055
2056				}, false);
2057
2058				canvas.addEventListener("mouseleave", function (e)
2059				{
2060					if (isDrawing)
2061					{
2062						findxy('out', e);
2063					}
2064				}, false);
2065			}
2066
2067			function draw()
2068			{
2069				ctx.beginPath();
2070				ctx.moveTo(prevX, prevY);
2071				ctx.lineTo(currX, currY);
2072				ctx.strokeStyle = x;
2073				ctx.lineWidth = y;
2074				ctx.stroke();
2075				ctx.closePath();
2076			}
2077
2078			function erase()
2079			{
2080				ctx.clearRect(0, 0, w, h);
2081			}
2082
2083
2084			function findxy(res, e)
2085			{
2086				var $canvas = getCanvas();
2087				var offset = $canvas.offset();
2088
2089				if (res == 'down')
2090				{
2091					prevX = currX;
2092					prevY = currY;
2093					currX = e.pageX - offset.left;
2094					currY = e.pageY - offset.top;
2095
2096					flag = true;
2097					dot_flag = true;
2098					if (dot_flag)
2099					{
2100						ctx.beginPath();
2101						ctx.fillStyle = x;
2102						ctx.fillRect(currX, currY, 2, 2);
2103						ctx.closePath();
2104						dot_flag = false;
2105					}
2106				}
2107				if (res == 'up' || res == "out")
2108				{
2109					flag = false;
2110				}
2111				if (res == 'move')
2112				{
2113					if (flag)
2114					{
2115						prevX = currX;
2116						prevY = currY;
2117						currX = e.pageX - offset.left;// - canvas.offsetLeft;
2118						currY = e.pageY - offset.top;// - canvas.offsetTop;
2119						draw();
2120					}
2121				}
2122			}
2123
2124			this.getValue = function ()
2125			{
2126				return canvas.toDataURL();
2127			}
2128
2129			this.validate = validate;
2130			function validate()
2131			{
2132				if (data.required)
2133				{
2134					return isDrawed;
2135				}
2136				else
2137				{
2138					return true;
2139				}
2140
2141			}
2142
2143
2144
2145		}
2146
2147		function TextArrayField(data)
2148		{
2149			TextField.call(this, data);
2150			var THIS = this;
2151			/*
2152			if (data.value !== null && data.value !== '')
2153			{
2154				try
2155				{
2156					_currentValue = JSON.parse(data.value);
2157				}
2158				catch (e)
2159				{
2160					_currentValue = [];
2161				}
2162			}
2163			*/
2164			var selectionFields = [];
2165			var currentIndex = 0;
2166			var $items = null;
2167			this.generateInput = function ()
2168			{
2169				var $container = $('<div class="text-array-field" />');
2170				$items = $('<div class="items" />').appendTo($container);
2171				addButtonFactory().appendTo($container);
2172				return $container;
2173			};
2174
2175
2176			this.onDisplay = function ($input, $label, $container)
2177			{
2178				$container.on('click', '[data-text-array-add-button]', addAnotherField);
2179			};
2180
2181			function addButtonFactory()
2182			{
2183				var $buttonsContainer = $('<div class="buttons" />');
2184				var $addButton = $('<a class="button" data-text-array-add-button />').appendTo($buttonsContainer);
2185				$('<i class="fa fa-plus" />').appendTo($addButton);
2186				return $buttonsContainer;
2187
2188			}
2189			function addAnotherField()
2190			{
2191				currentIndex++;
2192
2193				var d = $.extend(true, {}, data);
2194				d.name = d.name += '___' + currentIndex;
2195				d.question = data.ItemLabel;
2196				d.value = '';
2197				d.required = false;
2198
2199				var f = new TextField(d);
2200
2201				var $field = $('<div class="item" />').appendTo($items);
2202				f.addTo($field);
2203				selectionFields.push(f);
2204			}
2205
2206			this.getValue = function ()
2207			{
2208				var v = [];
2209				selectionFields.forEach(function (f)
2210				{
2211					var fieldValue = f.getValue();
2212					if (fieldValue !== '')
2213					{
2214						v.push(fieldValue);
2215					}
2216
2217				});
2218
2219				return JSON.stringify(v);
2220			};
2221		}
2222
2223
2224		$.require(['pageController', 'fields'], function (pageController, fieldsApi)
2225		{
2226			fieldsApi.add('autocomplete', AutoCompleteField);
2227
2228			function AutoCompleteField(data)
2229			{
2230				fieldsApi.all.text.call(this, data);
2231				var THIS = this;
2232				var ajax = pageController.ajax.run;
2233
2234				if (data.queryString === null)
2235				{
2236					data.queryString = {};
2237				}
2238
2239				if (data.appName !== null && data.appName !== '')
2240				{
2241					$.require(['accounts'], function (accounts)
2242					{
2243						var account = accounts.get(data.appName);
2244						if (account !== null)
2245						{
2246							ajax = account.$do;
2247						}
2248					});
2249				}
2250
2251				this.generateInput = function ()
2252				{
2253					var field = $('<input type="text" autocomplete="off" class="input-text input-autocomplete" data-selected-value="" />');
2254					return field;
2255
2256				};
2257
2258				var $selection = null;
2259				this.onDisplay = function ($input, $label, $container)
2260				{
2261					$selection = $('<div class="autocomplete-selection" />').hide().appendTo($container);
2262					$('<span />').appendTo($selection);
2263					$('<i class="close fa fa-times" />').appendTo($selection);
2264					$selection.on('click', '.close', function ()
2265					{
2266						$input.attr('data-selected-value', '');
2267						$input.val('');
2268						$selection.hide();
2269						$input.show();
2270						$input.trigger('autocompleteChanged');
2271					});
2272
2273
2274					$container.addClass('ui-front');
2275					$input.autocomplete({
2276						source: function (req, callback)
2277						{
2278							ajax({
2279								action: data.action,
2280								loader: false,
2281								loadingText: '',
2282								connector: false,
2283								vars: $.extend(true, data.queryString, {
2284									provider: data.provider,
2285									term: req.term
2286								}),
2287								onComplete: function (json)
2288								{
2289									callback(json.data);
2290								}
2291							});
2292						},
2293						open: function (e, ui)
2294						{
2295							$container.addClass('focused');
2296						},
2297						close: function (e, ui)
2298						{
2299							$container.removeClass('focused');
2300						},
2301						select: function (e, ui)
2302						{
2303							$input.attr('data-selected-value', ui.item.value);
2304							$input.val(ui.item.label);
2305							$input.hide();
2306							$selection.show();
2307							$selection.find('span').html(ui.item.label).show();
2308							$input.trigger('autocompleteChanged');
2309							THIS.triggerChanged();
2310							return false;
2311						},
2312						autoFocus: true
2313					}).autocomplete('instance')._renderItem = function (ul, item)
2314						{
2315							var $li = $('<li />').appendTo(ul);
2316							var $div = $('<div />').appendTo($li);
2317
2318							if (item.imageurl !== null && item.imageurl !== '')
2319							{
2320								$('<div class="image" />').css('backgroundImage', 'url(' + item.imageurl + ')').appendTo($div);
2321								$li.attr('data-image', true);
2322							}
2323
2324							$('<div class="ac-label" />').html(item.label).appendTo($div);
2325							return $li;
2326						};
2327
2328					if (data.value !== '')
2329					{
2330						$input.attr('data-selected-value', data.value);
2331						$input.hide();
2332						$selection.find('span').html(data.value);
2333						$selection.show();
2334					}
2335
2336
2337
2338				};
2339
2340				this.getValue = function ()
2341				{
2342					return THIS.$input.attr('data-selected-value');
2343				};
2344
2345				this.validate = function ()
2346				{
2347					return true;
2348				};
2349
2350				this.getFieldsToHide = function ()
2351				{
2352					if (typeof (data.fieldsToHideOnSelect) === 'undefined')
2353					{
2354						return [];
2355					}
2356
2357					if (data.fieldsToHideOnSelect === null)
2358					{
2359						return [];
2360					}
2361
2362					var currentValue = THIS.getValue();
2363					if (currentValue === '')
2364					{
2365						return [];
2366					}
2367
2368					return data.fieldsToHideOnSelect;
2369				};
2370
2371				this.receiveExpectedValue = receiveExpectedValue;
2372				function receiveExpectedValue(key, value)
2373				{
2374					if (typeof (data.queryString) === 'undefined' || data.queryString === null)
2375					{
2376						data.queryString = {};
2377					}
2378
2379					data.queryString[key] = value;
2380					//console.log(data.variable, 'received expected value from ' + key, value);
2381				}
2382			}
2383		});
2384
2385		function Form(data, callback)
2386		{
2387			var THIS = this;
2388			var FIELDS = {};
2389
2390			var $div = null;
2391			this.getElement = function ()
2392			{
2393				return $div;
2394			};
2395
2396			this.getField = function (fieldName)
2397			{
2398				if (typeof (FIELDS[fieldName]) === 'undefined')
2399				{
2400					console.warn('FieldsAPI.Form', 'field not found', fieldName, FIELDS);
2401					return null;
2402				}
2403
2404				return FIELDS[fieldName];
2405			}
2406
2407			this.getFields = function ()
2408			{
2409				return FIELDS;
2410			};
2411
2412			var $errorMessage = null;
2413
2414			this.toHtml = toHtml;
2415			function toHtml()
2416			{
2417				return $div;
2418				/*
2419				var div = $('<div class="do-input-panel" />');
2420				getBody().appendTo(div);
2421
2422				$errorMessage = $('<div class="error" />').hide().appendTo(div);
2423
2424				var $fields = $('<div class="fields" />').appendTo(div);
2425
2426				if (data.input.length < data.inputColumns)
2427				{
2428					data.inputColumns = data.input.length;
2429				}
2430
2431				$fields.attr('data-columns', data.inputColumns);
2432
2433
2434				data.input.forEach(function (fieldData)
2435				{
2436					var fieldDef = FIELDS_API.get(fieldData);
2437					var $field = fieldDef.addTo($fields);
2438
2439					FIELDS[fieldData.variable] = fieldDef;
2440				});
2441				getButtons().appendTo(div);
2442				getSubmitButton().appendTo(div);
2443				startListeners(div);
2444				$div = div;
2445				revealFields();
2446				updateSubmitButton();
2447				return div;
2448				*/
2449			}
2450
2451			function factory()
2452			{
2453				var div = $('<div class="do-input-panel" />');
2454				getBody().appendTo(div);
2455
2456				$errorMessage = $('<div class="error" />').hide().appendTo(div);
2457
2458				var $fields = $('<div class="fields" />').appendTo(div);
2459
2460				if (data.input.length < data.inputColumns)
2461				{
2462					data.inputColumns = data.input.length;
2463				}
2464
2465				$fields.attr('data-columns', data.inputColumns);
2466
2467
2468				data.input.forEach(function (fieldData)
2469				{
2470					var fieldDef = FIELDS_API.get(fieldData);
2471					var $field = fieldDef.addTo($fields);
2472
2473					FIELDS[fieldData.variable] = fieldDef;
2474				});
2475				$footer = getFooter().appendTo(div);
2476				$submitButtonContainer = getSubmitButton().appendTo(div);
2477				startListeners(div);
2478				revealFields();
2479				updateSubmitButton();
2480				return div;
2481			}
2482
2483			function getBody()
2484			{
2485				var div = $('<div />');
2486				if (data.inputBody !== '')
2487				{
2488					$('<div class="do-input-body" />').html(data.inputBody).appendTo(div);//.append(data.inputBody);
2489				}
2490
2491				if (typeof (data.errorMessage) === 'string' && data.errorMessage !== '')
2492				{
2493					$('<h3 class="error" />').html(data.errorMessage).appendTo(div);
2494				}
2495
2496				return div;
2497			}
2498
2499			var $submitButtonContainer = null;
2500			function getSubmitButton()
2501			{
2502				var div = $('<div class="do-input-container do-submit-container" />');
2503				if (data.submitButtonDisabled)
2504				{
2505					return div;
2506				}
2507				var button = $('<button class="do-submit" />').appendTo(div);
2508				$('<span />').html(data.inputSubmitLabel).appendTo(button);
2509				$('<i class="fa fa-chevron-right" />').appendTo(button);
2510				return div;
2511			}
2512
2513			var $footer = null;
2514			this.withFooter = withFooter;
2515			function withFooter($content)
2516			{
2517				$footer.append($content);
2518				return THIS;
2519			}
2520
2521			function getFooter()
2522			{
2523				var div = $('<div class="do-input-buttons do-footer" />');
2524				data.inputButtons.forEach(function (buttonData)
2525				{
2526					var button = $('<a class="button" />').doInputButton(buttonData);
2527					button.appendTo(div);
2528				});
2529				return div;
2530			}
2531
2532			function startListeners(panel)
2533			{
2534				panel.on('click', 'button.do-submit', function ()
2535				{
2536					onSubmit();
2537				});
2538
2539				panel.on('keyup', 'textarea', function (e)
2540				{
2541					updateSubmitButton();
2542					e.stopPropagation();
2543				});
2544
2545				panel.on('change', 'textarea', function (e)
2546				{
2547					//triggerOnChange();
2548					updateSubmitButton();
2549					e.stopPropagation();
2550				});
2551
2552				panel.on('keyup', 'input', function (e)
2553				{
2554					updateSubmitButton();
2555					if (e.which === 13)
2556					{
2557						onSubmit();
2558					}
2559				});
2560
2561				panel.on('change', 'select, input', function ()
2562				{
2563					revealFields();
2564					triggerOnChange(); //may cause problems?
2565					updateSubmitButton();
2566				});
2567
2568				panel.on('click', 'input[type="checkbox"]', function ()
2569				{
2570					revealFields();
2571					triggerOnChange();
2572					updateSubmitButton();
2573				});
2574
2575				panel.on('htmlchanged', function ()
2576				{
2577					triggerOnChange();
2578					updateSubmitButton();
2579				});
2580
2581				panel.on('autocompleteChanged', function ()
2582				{
2583					revealFields();
2584					triggerOnChange();
2585					updateSubmitButton();
2586
2587				});
2588
2589				panel.on('fieldValueChanged', function (e, sender)
2590				{
2591					var value = sender.getValue();
2592					$.each(FIELDS, function (key, field)
2593					{
2594						var expectedValues = field.getExpectedValues();
2595						if (expectedValues !== null)
2596						{
2597							if (expectedValues.indexOf(sender.variable) > -1)
2598							{
2599								field.receiveExpectedValue(sender.variable, value);
2600							}
2601						}
2602					});
2603					triggerOnChange();
2604					updateSubmitButton();
2605				});
2606
2607			}
2608
2609			this.validate = validate;
2610			function validate(addWarnings)
2611			{
2612				if (typeof (addWarnings) === 'undefined')
2613				{
2614					addWarnings = true;
2615				}
2616
2617				var result = true;
2618				$.each(FIELDS, function (key, field)
2619				{
2620					if (!field.validate())
2621					{
2622						result = false;
2623						if (addWarnings)
2624						{
2625							field.addWarning();
2626						}
2627					}
2628					else
2629					{
2630						if (addWarnings)
2631						{
2632							field.removeWarning();
2633						}
2634
2635					}
2636				});
2637				return result;
2638			}
2639
2640			this.getValues = getValues;
2641			function getValues()
2642			{
2643				var values = {};
2644
2645				data.input.forEach(function (fieldData)
2646				{
2647					var key = fieldData.variable;
2648					var field = FIELDS[key];
2649					var value = field.getValue();
2650
2651					if (typeof (values[key]) !== 'undefined')
2652					{
2653						console.warn('fieldsAPI', 'The field [' + key + '] is defined more than once...');
2654					}
2655
2656					values[key] = value;
2657
2658					var extraValues = field.getExtraValues();
2659					if (typeof (extraValues) === 'object')
2660					{
2661						$.each(extraValues, function (k, v)
2662						{
2663							values[key + k] = v;
2664
2665						});
2666					}
2667
2668					fieldData.value = value;
2669				});
2670
2671				return values;
2672			}
2673
2674			function isReadyToSubmit()
2675			{
2676				return validate(false);
2677			}
2678
2679			function updateSubmitButton()
2680			{
2681				$submitButtonContainer.attr('data-submit-ready', isReadyToSubmit());
2682				//$div.find('.do-submit-container').attr('data-submit-ready', isReadyToSubmit());
2683			}
2684
2685			function allowResubmit()
2686			{
2687				return $div.parent().attr('data-allow-resubmit') === 'true';
2688			}
2689
2690			var isAllowMultiSubmit = false;
2691			this.allowMultiSubmit = allowMultiSubmit;
2692			function allowMultiSubmit()
2693			{
2694				isAllowMultiSubmit = true;
2695				return THIS;
2696			}
2697
2698			var isSubmitted = false;
2699			function onSubmit()
2700			{
2701				if (!validate(true))
2702				{
2703					return;
2704				}
2705
2706				//if (!allowResubmit() && isSubmitted)
2707				if (!isAllowMultiSubmit && isSubmitted)
2708				{
2709					console.warn('fieldsapi.form', 'already submitted');
2710					return;
2711				}
2712
2713				clearErrrorMessage();
2714
2715				isSubmitted = true;
2716				callback(getValues(), function (errorMessage)
2717				{
2718					setErrorMessage(errorMessage);
2719					console.warn('fieldsapi.form', 'submit failed');
2720					isSubmitted = false;
2721					updateSubmitButton();
2722				});
2723			}
2724
2725			function revealFields()
2726			{
2727				var fieldsToReveal = [];
2728				var fieldsToHide = [];
2729
2730				$.each(FIELDS, function (name, field)
2731				{
2732					try
2733					{
2734						var toReveal = field.followUps.getFieldsToReveal(field.getValue());
2735						//console.log('Form.revealFields', toReveal);
2736						if (toReveal.length > 0)
2737						{
2738							toReveal.forEach(function (f)
2739							{
2740								fieldsToReveal.push(f);
2741							});
2742						}
2743
2744						var toHide = field.getFieldsToHide();
2745						if (toHide.length > 0)
2746						{
2747							toHide.forEach(function (f)
2748							{
2749								fieldsToHide.push(f);
2750							});
2751						}
2752
2753					}
2754					catch (e)
2755					{
2756						console.warn('fields-api.Form.revealFields()', e);
2757					}
2758
2759					field.unReveal(); //reset
2760
2761					/*
2762					//hide followUps here
2763					toReveal.forEach(function (fieldName)
2764					{
2765						if (typeof (FIELDS[fieldName]) !== 'undefined')
2766						{
2767							FIELDS[fieldName].unReveal();
2768							console.warn('Form', fieldName, 'unReveal');
2769						}
2770					});
2771					*/
2772					field.unHide();
2773				});
2774
2775				fieldsToReveal.forEach(function (fieldName)
2776				{
2777					if (typeof (FIELDS[fieldName]) !== 'undefined')
2778					{
2779						FIELDS[fieldName].reveal();
2780						//console.warn('Form', fieldName, 'reveal');
2781					}
2782				});
2783
2784				fieldsToHide.forEach(function (fieldName)
2785				{
2786					if (typeof (FIELDS[fieldName]) !== 'undefined')
2787					{
2788						FIELDS[fieldName].hide();
2789					}
2790
2791				});
2792			}
2793
2794			function clearErrrorMessage()
2795			{
2796				$errorMessage.html('').hide().removeClass('active');
2797			}
2798
2799			function setErrorMessage(message)
2800			{
2801				$errorMessage.html(message).show().addClass('active');
2802			}
2803
2804			var plugins = [];
2805			function addPlugins()
2806			{
2807				if (typeof (data.plugins) !== 'object')
2808				{
2809					return;
2810				}
2811
2812				if (data.plugins.length === 0)
2813				{
2814					return;
2815				}
2816
2817				plugins = [];
2818				data.plugins.forEach(function (pluginName)
2819				{
2820					var plugin = FIELDS_API.plugins.get(pluginName);
2821					if (plugin !== null)
2822					{
2823						plugins.push(new plugin(THIS));
2824					}
2825				});
2826
2827
2828			}
2829
2830			var onChangeCallbacks = [];
2831			this.onChange = onChange;
2832			function onChange(callback)
2833			{
2834				onChangeCallbacks.push(callback);
2835				return THIS;
2836			}
2837
2838			function triggerOnChange()
2839			{
2840				var values = getValues();
2841				onChangeCallbacks.forEach(function (cb)
2842				{
2843					cb(values);
2844				});
2845			}
2846
2847			function start()
2848			{
2849				$div = factory();
2850				addPlugins();
2851
2852			}
2853
2854			start();
2855		}
2856
2857		function TabbedForm(panelInputData, callback)
2858		{
2859			var THIS = this;
2860			var maxIndex = panelInputData.tabs.length - 1;
2861			var currentIndex = -1;
2862			var $slider = null;
2863			var $controls = null;
2864			var $element = null;
2865			var $breadcrumbsContainer = null;
2866			var $errorMessage = null;
2867			this.$breadcrumbsContainer = null;
2868			this.$pages = null;
2869			var tabs = [];
2870
2871			this.toHtml = toHtml;
2872			this.getElement = toHtml;
2873			function toHtml()
2874			{
2875				if ($element !== null)
2876				{
2877					return $element;
2878				}
2879
2880				var $container = $('<div class="do-input-tabbed-form" />');
2881
2882				if (typeof (panelInputData.inputBody) !== 'undefined')
2883				{
2884					if (panelInputData.inputBody !== null)
2885					{
2886						$('<div class="body" />').html(panelInputData.inputBody).appendTo($container);
2887					}
2888				}
2889
2890				if (typeof (panelInputData.errorMessage) === 'string' && panelInputData.errorMessage !== '')
2891				{
2892					$('<h3 class="error" />').html(panelInputData.errorMessage).appendTo($container);
2893				}
2894
2895
2896				$element = $container;
2897				$breadcrumbsContainer = $('<div class="breadcrumbs" />').appendTo($container);
2898				THIS.$breadcrumbsContainer = $breadcrumbsContainer;
2899
2900				$errorMessage = $('<div class="error-message" />').appendTo($container);
2901
2902				$slider = $('<div class="slider" />').appendTo($container);
2903				$slider.on('keyup', function ()
2904				{
2905					setTimeout(function ()
2906					{
2907						$slider.scrollLeft(0);
2908					}, 100);
2909				});
2910
2911				var $pages = $('<div class="category-sliders" />').appendTo($slider);
2912				THIS.$pages = $pages;
2913
2914				$controls = $('<div class="controls" />').appendTo($container);
2915				$('<a class="button previous" data-previous-button><i class="fa fa-chevron-left"></i><span>Previous</span></a>').appendTo($controls);
2916				$('<a class="button next" data-next-button><span>Next</span><i class="fa fa-chevron-right"></i></a>').appendTo($controls);
2917				var $submitButton = $('<a class="button submit" data-submit-button role="button" />').appendTo($controls);
2918				$('<span />').html(panelInputData.inputSubmitLabel).appendTo($submitButton);
2919				$('<i class="fa fa-chevron-right" />').appendTo($submitButton);
2920				setFieldTabIndexes();
2921				var index = 0;
2922				panelInputData.tabs.forEach(function (input)
2923				{
2924					tabs.push(new TabbedFormTab(THIS, input, index));
2925					index++;
2926				});
2927
2928				//getSubmitButton().appendTo($controls);
2929				startListeners($pages);
2930
2931				$controls.on('click', '.button.submit', function ()
2932				{
2933					onSubmit();
2934				});
2935
2936				$controls.on('click', '.button.next', function ()
2937				{
2938					next();
2939				});
2940
2941				$controls.on('click', '.button.previous', function ()
2942				{
2943					previous();
2944				});
2945
2946				$breadcrumbsContainer.on('click', '.breadcrumb', function ()
2947				{
2948					toPage($(this).index());
2949				});
2950
2951				updateSubmitButton();
2952				toPage(0);
2953				return $container;
2954			}
2955
2956
2957			function setFieldTabIndexes()
2958			{
2959				var index = 100;
2960				panelInputData.tabs.forEach(function (tab)
2961				{
2962					tab.input.forEach(function (field)
2963					{
2964						field.index = index;
2965						index++;
2966					});
2967				});
2968
2969			}
2970
2971
2972			function startListeners(panel)
2973			{
2974
2975				panel.on('keyup', 'textarea', function (e)
2976				{
2977					updateSubmitButton();
2978					e.stopPropagation();
2979				});
2980
2981				panel.on('keyup', 'input', function (e)
2982				{
2983					updateSubmitButton();
2984					/*
2985					if (e.which === 13)
2986					{
2987						if (currentIndex === maxIndex)
2988						{
2989							onSubmit();
2990						} else
2991						{
2992							next();
2993						}
2994
2995					}
2996					*/
2997				});
2998
2999				panel.on('focus', '.do-input-panel input,select,textarea', function ()
3000				{
3001					var index = $(this).parents().filter('.do-input-panel').index();
3002					toPage(index);
3003				});
3004
3005				panel.on('change', 'select', function ()
3006				{
3007					updateSubmitButton();
3008				});
3009
3010				panel.on('click', 'input[type="checkbox"]', function ()
3011				{
3012					updateSubmitButton();
3013				});
3014
3015				panel.on('htmlchanged', function ()
3016				{
3017					updateSubmitButton();
3018				});
3019
3020				panel.on('fieldValueChanged', function (e, sender)
3021				{
3022					updateSubmitButton();
3023					tabs.forEach(function (tab)
3024					{
3025						tab.receiveExpectedValue(sender);
3026					});
3027
3028				});
3029
3030
3031
3032			}
3033
3034			this.validate = validate;
3035			function validate(addWarnings)
3036			{
3037				if (typeof (addWarnings) === 'undefined')
3038				{
3039					addWarnings = true;
3040				}
3041
3042				var result = true;
3043				var firstInvalidatedTabIndex = null;
3044
3045				var index = 0;
3046				tabs.forEach(function (tab)
3047				{
3048					if (!tab.validate(addWarnings))
3049					{
3050						if (firstInvalidatedTabIndex === null)
3051						{
3052							firstInvalidatedTabIndex = index;
3053						}
3054						result = false;
3055					}
3056					index++;
3057				});
3058
3059				if (addWarnings)
3060				{
3061					if (firstInvalidatedTabIndex !== null)
3062					{
3063						toPage(firstInvalidatedTabIndex);
3064					}
3065				}
3066
3067				return result;
3068			}
3069
3070			function isReadyToSubmit()
3071			{
3072				return validate(false);
3073			}
3074
3075
3076			this.getValues = getValues;
3077			function getValues()
3078			{
3079				var values = {};
3080				tabs.forEach(function (tab)
3081				{
3082					$.each(tab.getValues(), function (k, v)
3083					{
3084						if (typeof (values[k]) !== 'undefined')
3085						{
3086							console.warn('fieldsAPI', 'The field [' + k + '] is defined more than once...');
3087						}
3088						values[k] = v;
3089					});
3090					//values = $.extend(values, tab.getValues());
3091				});
3092				return values;
3093			}
3094
3095			var isAllowMultiSubmit = false;
3096			this.allowMultiSubmit = allowMultiSubmit;
3097			function allowMultiSubmit()
3098			{
3099				isAllowMultiSubmit = true;
3100				return THIS;
3101			}
3102
3103			function onSubmit()
3104			{
3105				clearErrrorMessage();
3106				if (!validate())
3107				{
3108					setErrorMessage('<p>Please fill in the missing information outlined below.</p>');
3109					return;
3110				}
3111
3112				callback(getValues());
3113			}
3114
3115			function updateSubmitButton()
3116			{
3117				$controls.attr('data-submit-ready', isReadyToSubmit());
3118			}
3119
3120			function clearErrrorMessage()
3121			{
3122				$errorMessage.html('').removeClass('active');
3123			}
3124
3125			function setErrorMessage(message)
3126			{
3127				$errorMessage.html(message).addClass('active');
3128			}
3129
3130
3131			//CONTROLS
3132
3133
3134			function toPage(index)
3135			{
3136				if (currentIndex === index)
3137				{
3138					return;
3139				}
3140
3141				$slider.find('.do-input-panel').removeClass('active');
3142				$breadcrumbsContainer.find('.breadcrumb').removeClass('active');
3143
3144				//$slider.find('.do-input-panel').eq(index).addClass('active').data('TabbedFormTab').focus();
3145				$slider.find('.do-input-panel').eq(index).data('TabbedFormTab').focus();
3146				$element.attr('data-page', index);
3147				//$slider.addClass('started');
3148				currentIndex = index;
3149				//var $breadcrumbs = $breadcrumbsContainer.find('.breadcrumb');
3150				//$breadcrumbs.removeClass('active');
3151				//$breadcrumbs.eq(index).addClass('active');
3152				updateControls(currentIndex);
3153			}
3154
3155			function updateControls(currentIndex)
3156			{
3157				if (currentIndex === 0)
3158				{
3159					$controls.addClass('first');
3160				} else
3161				{
3162					$controls.removeClass('first');
3163				}
3164
3165				if (currentIndex === maxIndex)
3166				{
3167					$controls.addClass('last');
3168				} else
3169				{
3170					$controls.removeClass('last');
3171				}
3172
3173				updateSubmitButton();
3174			}
3175
3176			function scrollToTop()
3177			{
3178				window.scrollTo(0, $breadcrumbsContainer[0].getBoundingClientRect().top);
3179			}
3180
3181
3182			function next()
3183			{
3184				var index = currentIndex + 1;
3185				if (index > maxIndex)
3186				{
3187					index = maxIndex;
3188				}
3189				toPage(index);
3190				scrollToTop();
3191			}
3192
3193			function previous()
3194			{
3195				var index = currentIndex - 1;
3196				if (index < 0)
3197				{
3198					index = 0;
3199				}
3200				toPage(index);
3201				scrollToTop();
3202			}
3203
3204			var plugins = [];
3205			function addPlugins()
3206			{
3207				if (typeof (panelInputData.plugins) !== 'object')
3208				{
3209					return;
3210				}
3211
3212				if (panelInputData.plugins.length === 0)
3213				{
3214					return;
3215				}
3216
3217				plugins = [];
3218				panelInputData.plugins.forEach(function (pluginName)
3219				{
3220					var plugin = FIELDS_API.plugins.get(pluginName);
3221					if (plugin !== null)
3222					{
3223						plugins.push(new plugin(THIS));
3224					}
3225				});
3226
3227
3228			}
3229
3230			this.getField = getField;
3231			function getField(name)
3232			{
3233				var result = null;
3234				tabs.forEach(function (tab)
3235				{
3236					if (result === null)
3237					{
3238						result = tab.getField(name);
3239					}
3240
3241				});
3242
3243				if (result === null)
3244				{
3245					console.warn('Field not found', name);
3246				}
3247
3248				return result;
3249			}
3250
3251			function start()
3252			{
3253				addPlugins();
3254
3255			}
3256
3257			start();
3258		}
3259
3260		function TabbedFormTab(form, data, index)
3261		{
3262			var THIS = this;
3263			var FIELDS = {};
3264			var $breadcrumb = null;
3265			var $panel = null;
3266
3267			function start()
3268			{
3269				$breadcrumb = getTabBreadcrumb().appendTo(form.$breadcrumbsContainer);
3270				$panel = getTabHtml().appendTo(form.$pages);
3271
3272				
3273				$panel.on('change', 'select, input', function ()
3274				{
3275					revealFields();
3276				});
3277				
3278				$panel.on('click', 'input[type="checkbox"]', function ()
3279				{
3280					revealFields();
3281				});
3282
3283				$panel.on('autocompleteChanged', function ()
3284				{
3285					revealFields();
3286				});
3287				revealFields();
3288
3289				$panel.data('TabbedFormTab', THIS);
3290			}
3291
3292			function getTabBreadcrumb()
3293			{
3294				var $div = $('<div class="breadcrumb" />').attr('data-index', index);
3295				var name = $('<div class="name" />').appendTo($div);
3296				$('<span />').html(data.inputTitle).appendTo(name);
3297				$('<i class="fa fa-exclamation-triangle" />').prependTo(name);
3298				$('<i class="fa fa-check-circle" />').appendTo(name);
3299				return $div;
3300			}
3301
3302			var $fields = null;
3303
3304			function getTabHtml()
3305			{
3306				var div = $('<div class="do-input-panel" />');
3307				getBody().appendTo(div);
3308
3309				$fields = $('<div class="fields" />').appendTo(div);
3310
3311				if (data.input.length < data.inputColumns)
3312				{
3313					data.inputColumns = data.input.length;
3314				}
3315
3316				$fields.attr('data-columns', data.inputColumns);
3317
3318				data.input.forEach(function (fieldData)
3319				{
3320					var fieldDef = FIELDS_API.get(fieldData);
3321					var $field = fieldDef.addTo($fields);
3322
3323					FIELDS[fieldData.variable] = fieldDef;
3324				});
3325
3326				return div;
3327			}
3328
3329			function getBody()
3330			{
3331				var div = $('<div />');
3332
3333				if (data.inputBody !== '')
3334				{
3335					$('<div class="do-input-body" />').html(data.inputBody).appendTo(div);
3336				}
3337				return div;
3338			}
3339
3340			this.validate = validate;
3341			function validate(addWarnings)
3342			{
3343				if (typeof (addWarnings) === 'undefined')
3344				{
3345					addWarnings = true;
3346				}
3347
3348				var result = true;
3349
3350				if (addWarnings)
3351				{
3352					clearValidation();
3353				}
3354				
3355				$.each(FIELDS, function (key, field)
3356				{
3357					if (!field.validate())
3358					{
3359						result = false;
3360						if (addWarnings)
3361						{
3362							field.addWarning();
3363						}
3364						
3365					}
3366					else
3367					{
3368						if (addWarnings)
3369						{
3370							field.removeWarning();
3371						}
3372						
3373					}
3374				});
3375
3376				if (addWarnings)
3377				{
3378					if (result === false)
3379					{
3380						setAsValidationFailed();
3381					}
3382					else
3383					{
3384						setAsValidationSuccess();
3385					}
3386				}
3387				return result;
3388			}
3389
3390			function clearValidation()
3391			{
3392				$breadcrumb.removeClass('validation-failed').removeClass('validation-succeeded');
3393			}
3394
3395			function setAsValidationFailed()
3396			{
3397				$breadcrumb.addClass('validation-failed');
3398			}
3399
3400			function setAsValidationSuccess()
3401			{
3402				$breadcrumb.addClass('validation-succeeded');
3403			}
3404
3405			this.getValues = getValues;
3406			function getValues()
3407			{
3408				var values = {};
3409				$.each(FIELDS, function (name, field)
3410				{
3411					//var key = field.variable;
3412					//var field = FIELDS[key];
3413					if (typeof (values[name]) !== 'undefined')
3414					{
3415						console.warn('fieldsAPI', 'The field ' + name + ' is defined more than once');
3416					}
3417
3418					var value = field.getValue();
3419					values[name] = value;
3420					var extraValues = field.getExtraValues();
3421					if (typeof (extraValues) === 'object')
3422					{
3423						$.each(extraValues, function (k, v)
3424						{
3425							values[name+k] = v;
3426
3427						});
3428					}
3429
3430					//fieldData.value = value;
3431
3432				});
3433
3434				/*
3435				data.input.forEach(function (fieldData)
3436				{
3437					var key = fieldData.variable;
3438					var field = FIELDS[key];
3439					var value = field.getValue();
3440					values[key] = value;
3441					fieldData.value = value;
3442				});
3443				*/
3444				return values;
3445			}
3446
3447			function revealFields()
3448			{
3449				//console.log('revealFields()');
3450				var fieldsToReveal = [];
3451				var fieldsToHide = [];
3452				//var inputsToAdd = [];
3453
3454				$.each(FIELDS, function (name, field)
3455				{
3456					try
3457					{
3458						var toReveal = field.followUps.getFieldsToReveal(field.getValue());
3459						//console.log(name, toReveal);
3460						if (toReveal.length > 0)
3461						{
3462							toReveal.forEach(function (f)
3463							{
3464								fieldsToReveal.push(f);
3465							});
3466						}
3467
3468						var toHide = field.getFieldsToHide();
3469						if (toHide.length > 0)
3470						{
3471							toHide.forEach(function (f)
3472							{
3473								fieldsToHide.push(f);
3474							});
3475						}
3476
3477
3478						
3479						
3480					}
3481
3482					catch (e)
3483					{
3484						console.warn('fields-api.TabbedFormTab.revealFields()', e);
3485					}
3486
3487					field.unReveal(); //reset
3488					field.unHide();
3489				});
3490
3491				fieldsToReveal.forEach(function (fieldName)
3492				{
3493					if (typeof (FIELDS[fieldName]) !== 'undefined')
3494					{
3495						FIELDS[fieldName].reveal();
3496					}
3497					else
3498					{
3499						console.warn('fieldsToReveal', 'could not find field: ' + fieldName);
3500					}
3501				});
3502
3503				fieldsToHide.forEach(function (fieldName)
3504				{
3505					if (typeof (FIELDS[fieldName]) !== 'undefined')
3506					{
3507						FIELDS[fieldName].hide();
3508					}
3509					else
3510					{
3511						console.warn('fieldsToHide', 'could not find field: ' + fieldName);
3512					}
3513
3514				});
3515
3516				insertFields();
3517
3518				/*
3519				inputsToAdd.forEach(function (input)
3520				{
3521					input.input.forEach(function (fieldData)
3522					{
3523						if (typeof (FIELDS[fieldData.variable]) === 'undefined')
3524						{
3525							var fieldDef = FIELDS_API.get(fieldData);
3526							var $field = fieldDef.addTo($fields);
3527							FIELDS[fieldData.variable] = fieldDef;
3528							data.input.push(fieldData);
3529						}
3530					});
3531					console.log('inputToAdd', input);
3532				});
3533				*/
3534			}
3535
3536			function insertFields()
3537			{
3538				$.each(FIELDS, function (name, field)
3539				{
3540					insertFieldFields(field);
3541				});
3542			}
3543
3544			function insertFieldFields(field)
3545			{
3546				var toInserts = field.followUps.getFieldsToInsert();
3547				//console.log('insertFieldFields', field.variable, toInserts);
3548
3549				var inserted = [];
3550
3551				toInserts.forEach(function (fieldData)
3552				{
3553					inserted.push(fieldData.variable);
3554					if (typeof (FIELDS[fieldData.variable]) === 'undefined')
3555					{
3556						var fieldDef = FIELDS_API.get(fieldData);
3557						//var $field = fieldDef.addTo($fields);
3558						var $subForm = field.$container.find('.sub-form');
3559						if ($subForm.length === 0)
3560						{
3561							$subForm = $('<div class="sub-form" />').appendTo(field.$container);
3562						}
3563
3564						var $field = fieldDef.addTo($subForm);
3565
3566						$field.attr('data-inserted-by', field.variable);
3567						FIELDS[fieldData.variable] = fieldDef;
3568						//console.log('inserted', fieldData.variable);
3569					}
3570				});
3571
3572				$fields.find('[data-inserted-by="' + field.variable + '"]').each(function ()
3573				{
3574					var $field = $(this);
3575					var key = $field.data('variable');
3576					if (inserted.indexOf(key) === -1)
3577					{
3578						removeField(key);
3579					}
3580				});
3581
3582			}
3583
3584			function removeField(fieldVariable)
3585			{
3586				//console.log('removeField', fieldVariable);
3587				var fields = {};
3588				$.each(FIELDS, function (name, field)
3589				{
3590					if (name !== fieldVariable)
3591					{
3592						fields[name] = field;
3593					}
3594				});
3595
3596				$fields.find('[data-variable="' + fieldVariable + '"]').remove();
3597				FIELDS = fields;
3598			}
3599
3600			this.receiveExpectedValue = receiveExpectedValue;
3601			function receiveExpectedValue(sender)
3602			{
3603				var value = sender.getValue();
3604				$.each(FIELDS, function (key, field)
3605				{
3606					var expectedValues = field.getExpectedValues();
3607					if (expectedValues !== null)
3608					{
3609						if (expectedValues.indexOf(sender.variable) > -1)
3610						{
3611							field.receiveExpectedValue(sender.variable, value);
3612						}
3613					}
3614				});
3615			}
3616
3617			this.focus = focus;
3618			function focus()
3619			{
3620				$panel.addClass('active');
3621				$breadcrumb.addClass('active');
3622				$.each(FIELDS, function (k, field)
3623				{
3624					field.onTabFocus();
3625				});
3626			}
3627
3628			this.getField = getField;
3629			function getField(name)
3630			{
3631				if (typeof (FIELDS[name]) === 'undefined')
3632				{
3633					return null;
3634				}
3635
3636				return FIELDS[name];
3637
3638			}
3639
3640			start();
3641		}
3642
3643	});
3644
3645
3646})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.