PageSourceSearch

https://www.visitmorningtonpeninsula.org/DesktopModules/DnnSharp/A…/templates/Form/bootstrap/script.js?cdv=365

js visitmorningtonpeninsula.org collected 2026-10-02 07:07:05 UTC 111,195 bytes, 2,364 lines download raw bytes

1dnnsfAngular15.module('blueimp.fileupload', []);
2dnnsfAngular15.module('afControls', ['ngSanitize', 'dnnsf', 'blueimp.fileupload']);
3var afApp = (function ($, angular) {
4    var formRoot;
5    const timeZoneOffset = new Date().getTimezoneOffset();
6    // --------------------------------------------------------------------------------------------------------------------------------
7    /// Start Action Form template specific code
8
9    angular.module('afControls').factory('afSettings', function () {
10        return {
11            settings: {}
12        }
13    });
14
15    function ActionFormCtrl($scope, $http, $timeout, $sce, $cookieStore, $element, dataSources, dnnsf, afSettings, dnnsfHttp) {
16        var $ = dnnsfjQuery;
17        $scope.dnnsf = dnnsf;
18        $scope.$sce = $sce;
19        $scope.Math = Math;
20        $scope.testTags = [];
21
22        $scope.form = {
23            fields: []
24        };
25        // get the field based on the html element.
26        $scope.getField = function (htmlElement) {
27            var field = _.find(_.values($scope.form.fields), function (_field) {
28                return _field.id === htmlElement.id;
29            });
30            if (!field) return null;
31            return field;
32        };
33
34        $scope.controls = [];
35        $scope.registerControl = function (control) {
36            $scope.controls.push(control);
37        };
38
39        $scope.parse = function (val) {
40            if (val === undefined || val === null)
41                return false;
42
43            if ($.isNumeric(val))
44                return parseFloat(val);
45
46            // remove comma - this only works for locales that use comma for thousands
47            val = val.constructor === Array ? val.toString() : val.toString().replace(/,(\d{3})/g, '$1');
48            if ($.isNumeric(val))
49                return parseFloat(val);
50
51            if (val.toLowerCase() == "true")
52                return true;
53
54            if (val.toLowerCase() == "false")
55                return false;
56
57            return val;
58        };
59
60        $scope.load = function (mid) {
61            formRoot = $('#dnn' + mid + 'root');
62            var res = dnnsf.api.actionForm.getSettings(mid);
63            if (res.Data.error) {
64                alert('Error: ' + res.Data.error);
65                return;
66            }
67
68            // todo: handle errors
69            $scope.form = res.Data;
70            $scope.settings = afSettings.settings = res.Settings;
71            $scope.$$ = $; // just a hack to bypass angular
72
73            $scope.form.dirty = function () {
74                return formRoot.find('.ng-dirty').length > 0;
75            }
76
77            //closing the dropdown(multiple choice - dropdown with checkboxes) when clicking outside the container
78            $(document).mouseup(function (e) {
79                var container = $(".field-container.checkbox-list .panel.panel-default");
80                if (!container.is(e.target)
81                    && container.has(e.target).length === 0) {
82                    $.each($scope.form.fields, function (i, field) {
83                        if (field.show) {
84                            setTimeout(function () {
85                                field.show = false;
86                                $scope.$apply();
87                            }, 0);
88                        }
89                    });
90                }
91            });
92
93            $.each(res.Settings.Fields, function (i, f) {
94                res.Settings.Fields[f.TitleCompacted] = f;
95            });
96
97            var createOnChangeHandler = function (_scope, fieldId, onChangeScript) {
98                var refresh = function () {
99                    // this is an Anti-Pattern https://github.com/angular/angular.js/wiki/Dev-Guide%3A-Anti-Patterns
100                    // it is recommended to use $timeout instead https://stackoverflow.com/a/18996042/499220
101                    console.warn('The refresh() method is obsolete. Please use the $timeout service that is now provided in the context of the script execution.');
102
103                    var scope = _scope;
104                    while (scope) {
105                        if (scope.$$phase) {
106                            return;
107                        }
108                    }
109
110                    _scope.$apply();
111                };
112
113                // Function constructor arguments information: Function(...args: string[], functionBody: string)
114                // a function with arguments named and ordered by the provided `args` parameters and `functionBody` as executable code will be generated.
115                // e.g. Function('form', 'scope', code) => function(form, scope) { /* code */ }
116                var onChangeFunc = new Function('form', 'item', 'scope', 'refresh', '$timeout', onChangeScript);
117
118                return function (form, item) {
119                    try {
120                        var field = $('#' + fieldId);
121
122                        var allowSubmit = onChangeFunc.call(field, form, item, _scope, refresh, $timeout);
123
124                        var fieldElement = field[0];
125                        if (fieldElement) {
126                            fieldElement.preventSubmit = false;
127                            if (allowSubmit === false) {
128                                fieldElement.preventSubmit = true;
129                            }
130                        }
131                    } catch (e) {
132                        console.error('Error running Action Form on change script', e);
133                    }
134                }
135            }
136
137            $.each($scope.form.fields, function (k, field) {
138
139                if (field.onChange) {
140                    // This will be called from `data-ng-change` attributes that are generated by xsl (e.g. attr-common.xsl)
141                    field.onChange = createOnChangeHandler($scope, field.id, field.onChange);
142                }
143
144                if (field.options) {
145                    // this is a dropdown, initialize ddValue and tbValue
146                    field.ddValue = field.tbValue = field.value;
147
148                    // add a getValue function on dropdowns, which will be used by validators to get the proper value for validation
149                    if (_.find(['closed-multiple-dropdown', 'closed-multiple-checkbox', 'dropdown-checkboxes'],
150                        function (type) { return field.type == type; })
151                    ) {
152                        field.getValue = function (value) {
153                            return value.indexOf('/') === 0 ? value.substring(1) : value;
154                        }
155                    }
156
157                    if (field.type == 'closed-multiple-checkbox' || field.type == 'dropdown-checkboxes') {
158                        var selItems = field.value;
159                        $.each(field.options, function (k, oItem) {
160                            oItem.selected = $.inArray(oItem.value, selItems) != -1;
161                        });
162                    } else {
163                        for (var i = 0; i < field.options.length; i++)
164                            if (field.options[i].value == field.value) {
165                                field.selected = field.options[i]; break;
166                            }
167                    }
168
169                    // if it has "other" option and value doesn't exist in dropdown, switch dd to other
170                    var other = $.grep(field.options, function (oOpt, iOpt) { return oOpt.filter == 'other'; });
171                    other = other.length ? other[0] : null;
172
173                    if (other && field.value && $.grep(field.options, function (oOpt, iOpt) { return oOpt.value == field.value; }).length == 0) {
174                        field.ddValue = other.value;
175                        field.otherValue = field.value;
176                        field.selected = _.filter(field.options, function (o) { return o.filter == "other"; })[0];
177                    }
178
179                    if (field.type == 'address-region') {
180
181                        $scope.setRegionFieldValue = function (source) { // source: 'dropdown' | 'textbox' | 'other'
182                            switch (source) {
183                                case 'dropdown': {
184                                    if (field.ddValue !== 'Other') {
185                                        field.tbValue = field.otherValue = '';
186                                        field.value = field.ddValue;
187                                        field.showOtherTextbox = false;
188
189                                        if (field.value && field.countryField) {
190                                            var selectedRegion = _.find($scope.countries[field.countryField].regions, function (obj) {
191                                                return obj.value == field.value;
192                                            });
193                                            field.viewValue = selectedRegion ? selectedRegion.text : field.value;
194                                        }
195                                    } else {
196                                        field.value = '';
197                                        field.showOtherTextbox = true;
198                                        field.otherValue = field.value;
199                                    }
200                                    break;
201                                }
202                                case 'textbox': {
203                                    field.ddValue = field.otherValue = '';
204                                    field.value = field.tbValue;
205                                    break;
206                                }
207                                case 'other': {
208                                    field.ddValue = 'Other';
209                                    field.value = field.otherValue;
210                                    field.showOtherTextbox = true;
211                                    break;
212                                }
213                            }
214                        }
215
216                    }
217                    // if it's a checkbox list, this is also needed
218                    if (field.type == 'closed-multiple-checkbox' || field.type == 'dropdown-checkboxes') {
219                        $scope.concatValues(field);
220                        // also, watch for changes
221
222                        $scope.$watch('form.fields.' + field.name + '.value', function () {
223                            var selItems = field.value;
224                            $.each(field.options, function (k, oItem) {
225                                oItem.selected = $.inArray(oItem.value, selItems) != -1;
226                            });
227                        }, true);
228
229                        field.checkAll = function () {
230                            $.each(field.options, function (k, oItem) {
231                                if (field.visible !== false)
232                                    oItem.selected = true;
233                            });
234                            setTimeout(function () {
235                                field.onChange && field.onChange($scope.form, field);
236                                $("[name='" + field.id + "']").valid();
237                            }, 0)
238                        };
239
240                        field.uncheckAll = function () {
241                            $.each(field.options, function (k, oItem) {
242                                if (field.visible !== false)
243                                    oItem.selected = false;
244                            });
245                            setTimeout(function () {
246                                field.onChange && field.onChange($scope.form, field);
247                                $("[name='" + field.id + "']").valid();
248                            }, 0)
249                        };
250                    } else {
251                        $scope.$watch('form.fields.' + field.name + '.value', function (newValue, oldValue) {
252                            // this allows setting the value of dropdown directly in the model
253                            // but watch out if we have multiple entries with same value filtered away
254                            newValue !== oldValue && setDropdownSelected(field, newValue);
255                        });
256                    }
257
258                    if (field.linkedTo) {
259                        if (!_.find(field.options, function (option) { return field.tbValue.constructor === Array ? $.inArray(option.value, field.tbValue) != -1 : option.value == field.tbValue })) {
260                            field.value = '';
261                        }
262
263                        $.each($.map(field.linkedTo.split(','), function (x) { return x.trim() }), function (i, linkedField) {
264                            $scope.$watch('form.fields.' + linkedField + '.value', function (newValue, oldValue) {
265
266                                if (typeof newValue != 'undefined' && !angular.equals(newValue, oldValue)) {
267                                    console.log($scope.form.fields[linkedField]);
268                                    $scope.getFieldData(field);
269                                }
270
271                            });
272                            $scope.$watch('form.fields.' + linkedField + '.options', function (newValue, oldValue) {
273
274                                if (typeof newValue != 'undefined' && $scope.form.fields[linkedField].linkedTo) {
275                                    $scope.getFieldData(field);
276                                }
277
278                            }, true);
279                        });
280                    }
281
282                    // also group options by filters for easy iteration
283                    if (field.options.length) {
284
285                        // this is used by the permission grid
286                        field.optionsFilters = []; // the object below loses order, so we're going to use this one for iterations
287                        $.each(field.options, function (ii, oo) {
288                            if ($.inArray(oo.filter, field.optionsFilters) == -1)
289                                field.optionsFilters.push(oo.filter);
290                        });
291
292                        field.optionsNames = [];// the object below loses order, so we're going to use this one for iterations
293                        field.optionsByName = {};
294                        $.each(field.options, function (ii, oo) {
295                            if (!field.optionsByName[oo.text]) {
296                                field.optionsNames.push(oo.text);
297                                field.optionsByName[oo.text] = [];
298                            }
299                            field.optionsByName[oo.text].push(oo);
300                        });
301                    }
302
303                }
304
305                if (field.type === 'open-dnn-editor') {
306                    var dnnTextEditorIframe = $('#' + field.id);
307                    if (dnnTextEditorIframe.length && dnnsf.canAccessIFrame(dnnTextEditorIframe[0]) && dnnTextEditorIframe[0].contentWindow.getContent) {
308                        dnnTextEditorIframe.attr("data-content", field.value);
309                        dnnTextEditorIframe[0].contentWindow.setContent(field.value);
310                    }
311                }
312            });
313
314            // done intializing fields, call on load handler if any
315            if ($scope.form.onLoad) {
316                eval('( function(form) { var $scope = scope = this; try { ' + $scope.form.onLoad + '; } catch (e) { console.log(\'Error running Action Form on load script\', e); } } )')
317                    .call($scope, $scope.form);
318            }
319
320            if ($scope.form.SaveInCookie) {
321                var saveInCookiesTimer;
322                function saveInCookies() {
323                    $timeout.cancel(saveInCookiesTimer);
324                    saveInCookiesTimer = $timeout(function () {
325
326                        var saveData = getFormData($element.closest('.form-root'));
327                        dnnsfHttp($scope.settings.ModuleId, {
328                            method: 'POST',
329                            url: $scope.form.submitUrl + "&event=autosave&submission=" + ($scope.form.submissionKey || ''),
330                            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
331                            data: $.param(saveData)
332                        }).success(function (data, status) {
333                            $cookieStore.put($scope.form.SaveInCookie, data.submissionKey, { path: '/', expires: 365 });
334                        });
335
336                    }, 500);
337                }
338
339                $scope.$watch('form.fields', saveInCookies, true);
340            }
341            $scope.showLoader = false;
342            formRoot.find('.c-form.hidden').removeClass('hidden');
343
344            dnnsf.events.emit('formLoaded');
345
346        };
347
348        function setDropdownSelected(field, val) {
349            for (var i = 0; i < field.options.length; i++)
350                if (field.options[i].value == val) {
351                    field.selected = field.options[i];
352                    break;
353                }
354        }
355
356        $scope.refreshCaptcha = function (mid, fieldName) {
357            var field = $scope.form.fields[fieldName];
358
359            var svcFramework = dnnsfjQuery.ServicesFramework(mid);
360            var apiUrl = svcFramework.getServiceRoot("DnnSharp/ActionForm");
361
362            dnnsfHttp($scope.settings.ModuleId, {
363                method: 'GET',
364                url: apiUrl + 'RefreshField/Captcha?_portalId= ' + dnnsf.portalId +
365                    '&tabId=' + dnnsf.tabId +
366                    '&_alias=' + dnnsf.alias +
367                    '&_mid=' + mid +
368                    '&language=' + dnnsf.locale +
369                    '&fieldId=' + field.fieldId +
370                    '&fieldName=' + fieldName,
371                headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
372            }).then(function (response) {
373                $timeout(function () {
374                    $scope.form.fields[fieldName].value = response.data.CaptchaEncrypted;
375                    $('#dnn' + mid + fieldName + 'captchaenc').val(response.data.CaptchaEncrypted);
376                    $('#dnn' + mid + 'root').find("img[data-fieldid=" + field.fieldId + "]").attr('src', response.data.ImageUrl);
377                });
378            });
379        }
380
381        $scope.getFieldData = function (field) {
382            $timeout(function () {
383                // get list from server
384                var saveData = getFormData($element.closest('.form-root'));
385                field.$_loading = true;
386
387                dnnsfHttp($scope.settings.ModuleId, {
388                    method: 'POST',
389                    url: $scope.form.getItemsUrl + "&fieldId=" + $scope.form.fields[field.name].fieldId + '&fieldName=' + field.name,
390                    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
391                    data: $.param(saveData)
392                }).then(function (result) {
393                    var data = result.data;
394                    $('#' + field.id).closest('.field-container')
395                        .removeClass('has-error')
396                        .find('span.text-danger')
397                        .html('');
398                    if (data.error) {
399                        data.validationErrors = [{ 'fieldId': field.name, 'message': data.error }];
400                        delete data.error;
401                        parseFormResponse2(formRoot, null, data);
402                        field.$_loading = false;
403                        field.options = [];
404                        field.value = '';
405                        return;
406                    }
407                    field.options = data;
408                    var found = false;
409                    for (var i = 0; i < field.options.length; i++)
410                        if (field.tbValue.constructor === Array ? $.inArray(field.options[i].value, field.tbValue) != -1 : field.options[i].value == field.tbValue) {
411                            field.selected = field.options[i];
412                            if (field.tbValue.constructor === Array) {
413                                $.each(field.tbValue, function (i, v) {
414                                    $.each(field.options, function (j, k) {
415                                        k.value == v && (field.options[j].selected = true);
416                                        found = true;
417                                    });
418                                });
419                            } else {
420                                _.forEach(field.options, function (option, index) {
421                                    if (option.value === field.tbValue) {
422                                        option.selected = true;
423                                        found = true;
424                                        return false;
425                                    }
426                                });
427                            }
428                            field.value = field.tbValue; //need it to get 
429                            break;
430                        }
431                    if (!found)
432                        field.value = '';
433                    $scope.concatValues(field);
434                    field.$_loading = false;
435                });
436            });
437        };
438
439        $scope.concatValues = function (ctl) {
440            if (!ctl.options)
441                return;
442            var vals = [];
443            var texts = [];
444            $.each(ctl.options, function (k, o) {
445                if (o.selected && o.visible !== false) {
446                    vals.push(o.value);
447                    texts.push(o.text);
448                }
449            });
450            ctl.value = vals;
451            if (ctl.selectedItemsText)
452                texts.length == 0
453                    ?
454                    ctl.text = localization.dropdownNoSelection
455                    :
456                    ctl.text = localization.dropdownSelectedBefore + ' ' + texts.length + ' ' + localization.dropdownSelectedAfter;
457            else
458                texts.length == 0 ? ctl.text = localization.dropdownNoSelection : ctl.text = texts.join(',');
459        };
460
461        // holds a list of region fields bound to each country
462        $scope.countries = {};
463
464        $scope.wireRegion = function (regionField, countryField) {
465            if (!$scope.countries[countryField])
466                $scope.countries[countryField] = { regionFields: [] };
467            $scope.countries[countryField].regionFields.push(regionField);
468
469            $scope.loadRegions(regionField, countryField, function () {
470                // select current region by code or by name
471                var field = $scope.form.fields[regionField];
472                var regions = $scope.countries[countryField].regions;
473
474                if (regions.length) {
475
476                    for (var i = 0; i < regions.length; i++)
477                        if (regions[i].value == field.value) {
478                            $scope.form.fields[regionField].ddValue = regions[i].value;
479                            return;
480                        }
481                    // check by text
482                    for (var i = 0; i < regions.length; i++)
483                        if (regions[i].text == field.value) {
484                            $scope.form.fields[regionField].ddValue = regions[i].value;
485                            return;
486                        }
487                    if (field.otherTextbox && $scope.initCountry) {
488                        $scope.initCountry = false;
489                        var isValueInOptions = $.grep(field.options, function (option, iOpt) { return option.value == field.value; }).length > 0;
490                        if (!isValueInOptions && field.value) {
491                            field.ddValue = 'Other';
492                            field.otherValue = field.value;
493                            field.showOtherTextbox = true;
494                            return;
495                        }
496                    }
497                    $scope.setRegionFieldValue('dropdown');
498                }
499            });
500        };
501
502        $scope.loadRegions = function (regionField, countryField, fnDone) {
503            dnnsf.log('loadRegions', countryField, $scope.countries[countryField], $scope.form.fields[countryField]);
504
505            $scope.$watch('form.fields.' + countryField, function () {
506                if (!$scope.countries[countryField] || !$scope.form.fields[countryField])
507                    return;
508
509                $scope.countries[countryField].loading = true;
510
511                var field = $scope.form.fields[regionField];
512                var data = {};
513                data[countryField] = $scope.form.fields[countryField].value;
514
515                dnnsfHttp($scope.settings.ModuleId, {
516                    method: 'POST',
517                    url: $scope.form.getItemsUrl + "&fieldId=" + field.fieldId + '&fieldName=' + field.name,
518                    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
519                    data: $.param(data)
520                }).then(function (result) {
521                    $scope.countries[countryField].loading = false;
522                    $scope.countries[countryField].regions = result.data;
523                    if ($scope.countries[countryField].regions.length) {
524                        // reset all textboxes values
525                        $.each($scope.countries[countryField].regionFields, function (i, regionField) {
526                            $scope.form.fields[regionField].countryField = countryField;
527                            if ($scope.form.fields[regionField]) {
528                                $scope.form.fields[regionField].ddValue = "";
529                                $scope.form.fields[regionField].tbValue = "";
530                                $scope.form.fields[regionField].otherValue = "";
531                            }
532                        });
533                    } else {
534                        // reset all dropdown values
535                        $.each($scope.countries[countryField].regionFields, function (i, regionField) {
536                            if ($scope.form.fields[regionField]) {
537                                $scope.form.fields[regionField].ddValue = "";
538                                $scope.form.fields[regionField].tbValue = "";
539                                $scope.form.fields[regionField].otherValue = "";
540                            }
541                        });
542                    }
543
544                    fnDone && fnDone();
545                });
546            }, true);
547
548        };
549
550        $scope.showSelected = function (node, selected, name) {
551            if (selected) {
552                $scope.form.fields[name].value = node.value;
553                $scope.form.fields[name].text = node.text;
554            } else {
555                $scope.form.fields[name].value = "";
556                $scope.form.fields[name].text = "";
557            }
558        }
559
560        $scope.closeDropdown = function (e, name) {
561            $scope.form.fields[name].showdrop = false;
562            $scope.$apply();
563        }
564
565        $scope.uptStarRating = function (value, name) {
566            $scope.form.fields[name].value = value;
567        }
568
569        $scope.updateField = function (field, val) { //used for dropdown with autocomplete and sortable-input
570            $scope.form.fields[field].value = val;
571            if ($scope.form.fields[field].options && $scope.form.fields[field].options.length) {
572                setDropdownSelected($scope.form.fields[field], val);
573            }
574            $scope.form.fields[field].onChange && $scope.form.fields[field].onChange($scope.form, field);
575        }
576    }
577    ActionFormCtrl.$inject = ['$scope', '$http', '$timeout', '$sce', '$cookieStore', '$element', 'dataSources', 'dnnsf', 'afSettings', 'dnnsfHttp'];
578
579    var initForm = function (options, fnDone) {
580        var formRoot;
581        var svcFramework = dnnsfjQuery.ServicesFramework(options.moduleId);
582        options.apiUrl = svcFramework.getServiceRoot("DnnSharp/ActionForm");
583
584        options.adminApiUrl = options.virtualDirectory + "/DesktopModules/DnnSharp/ActionForm/AdminApi.ashx";
585
586        if (options) {
587            dnnsf.portalId = options.portalId;
588        }
589        //openMode: Always, Page, Popup, Inline, Manual
590        var $ = dnnsfjQuery;
591        var formSettings = dnnsf.api.actionForm.getSettings(options.moduleId);
592        var queryString = $.extend(
593            {},
594            dnnsf.getUrlParts(location.search).query,
595            formSettings && !$.isEmptyObject(formSettings.passQs) && formSettings.passQs,
596            options.qs && !$.isEmptyObject(options.qs) && options.qs
597        );
598        dnnsf.api.actionForm.patchSettings(options.moduleId, { options: options });
599        $('#' + options.rootElementClientId).addClass(options.cssName);
600        $('#' + options.rootElementClientId).attr({ 'af-name': options.popupSettings.name, 'data-moduleid': options.moduleId }); //for openPopupByName()
601        if (options.openMode != "Always" && !options.manualMode) {
602            window['showFormPopup' + options.moduleId] = function () {
603                dnnsf.api.actionForm.openPopupById(options.moduleId);
604            };
605            window['hideFormPopup' + options.moduleId] = function () {
606                $('#dnn' + options.moduleId + 'popup').modal('hide')
607            }
608            window['showFormInline' + options.moduleId] = function () {
609                dnnsf.api.actionForm.initForm(options.moduleId);
610                showFormInline(options.moduleId, options.rootElementClientId);
611            }
612            window['hideFormInline' + options.moduleId] = function () {
613                hideFormInline(options.moduleId, options.rootElementClientId);
614            }
615            if (!options.manualMode && options.openMode != "Always" && options.openMode != "Manual")
616                $('#' + options.rootElementClientId).html($('<div class="frontEndTemplate"></div>').html(options.frontEndTemplate));
617
618            return;
619        } else {
620            setTimeout(function () {
621                options.showLoading && $('#' + options.rootElementClientId + ' > .common-loading-container').show();
622                options.tabsProLoading && $('#' + options.rootElementClientId).closest('.tab-pane').hasClass('active') && dnnsf.events.broadcast('loadForm', { 'loading': true, moduleId: options.moduleId });
623                dnnsf.initStickyLoading(options.rootElementClientId);
624            }, 0);
625
626            (!options.manualMode && options.openMode != "Always") &&
627                $('#' + options.rootElementClientId).html($('<div class="frontEndTemplate"></div>').html(options.frontEndTemplate));
628        }
629        // ^^backward compatibility for showFormPopup, hideFormPopup, showFormInline, hideFormInline functions^^
630        // getSettings
631        $.extend(queryString, {
632            '_portalId': options.portalId,
633            'referrer': document.referrer,
634            'openMode': options.openMode === "Manual" ? options.manualMode : options.openMode,
635            '_tabId': options.tabId,
636            '_alias': options.alias,
637            '_mid': options.moduleId,
638            '_url': document.URL
639        });
640
641        $.ajax({
642            headers: {
643                "DNNSF-Time-Offset": timeZoneOffset,
644                "RequestVerificationToken": svcFramework.getAntiForgeryValue()
645            },
646            url: options.apiUrl + '/settings/initializeForm?' + $.param(queryString) + (options.dnnPageQuery ? '&' + options.dnnPageQuery : ''),
647            type: "post",
648            data: options.requestForm,
649            async: true,
650            success: function (res) {
651                res.CssIncludes.forEach(function (cssPath) {
652                    dnnsf.includeCss(cssPath);
653                });
654
655                if (!res.Html) {
656                    dnnsf.api.actionForm.hideFormLoading(options.moduleId);
657                }
658                $('#' + options.rootElementClientId).find('.af-alert').remove();
659                options.hasErrors = false;
660
661                if (res.ResultType == "ActionResult") {
662                    options.manualMode == "Popup" && (dnnsf.api.actionForm.isFormPopupOpen['formPopup' + options.moduleId] = false); // the popup will not be opened because of the error/message
663                    parseFormResponse(res.ActionResult, {
664                        error: function (err) {
665                            options.hasErrors = true;
666                            var pnlMessage = $('<div class="af-alert alert alert-danger"></div>').html(res.ActionResult.Error);
667                            $('#' + options.rootElementClientId).html($('<div class="frontEndTemplate"></div>').append(pnlMessage));
668                        }
669                    });
670                }
671                res.Settings.apiUrl = options.apiUrl;
672                res.Settings.timeZoneOffset = timeZoneOffset;
673                dnnsf.api.actionForm.patchSettings(options.moduleId, res);
674                if (!$.isEmptyObject(res.ActionResult) && res.ActionResult.Content) {
675                    formRoot = $(res.ActionResult.Content);
676                    var pnlMessage = $('<div class="af-alert alert alert-info"></div>').append(formRoot);
677                    $('#' + options.rootElementClientId).append(pnlMessage);
678                } else {
679                    $('#' + options.rootElementClientId).find('#dnn' + options.moduleId + 'root').length ?
680                        $('#dnn' + options.moduleId + 'root').replaceWith(res.Html) :
681                        $('#' + options.rootElementClientId).append($.parseHTML(res.Html));
682                    dnnsf.loadJsFromHtml(res.Html);
683                    formRoot = $('#dnn' + options.moduleId + 'root');
684                }
685
686                if (res.JsIncludes.length) {
687                    res.JsIncludes.forEach(function (jsPath, index, array) {
688                        if (index !== array.length - 1) {
689                            $.getScript(jsPath);
690                        } else {
691                            $.getScript(jsPath, function () {
692                                initFormController();
693                                fnDone && fnDone();
694                            });
695                        }
696                    });
697                } else {
698                    initFormController();
699                    fnDone && fnDone();
700                }
701
702            },
703            error: function (err) {
704                console.error(this.url, err)
705                options.hasErrors = true;
706                var pnlMessage = $('<div class="af-alert alert alert-info"></div>').html(err.responseText);
707                $('#' + options.rootElementClientId).html($('<div class="frontEndTemplate"></div>').append(pnlMessage));
708            }
709        });
710
711        function initFormController() {
712
713            if (options.hasErrors) return;
714            if (!formRoot.length || formRoot[0].initialized)
715                return;
716
717            formRoot[0].onFormSubmit = formRoot[0].onFormSubmit || [];
718            formRoot[0].initialized = true;
719
720            // init common features
721            dnnsf.init($, options);
722            dnnsf.localization = localization = dnnsf.api.actionForm.getSettings(options.moduleId).Localization;
723            // call localization inside fileupload-validate
724            dnnsf.useLocalization && dnnsf.useLocalization();
725
726            var app = angular.module('ActionForm' + formRoot.attr('id'), ['ngAnimate', 'ngSanitize', 'dnnsf', 'afControls', 'ui.bootstrap', 'ui.bootstrap.datetimepicker', 'angucomplete', 'treeControl', 'ngstars', 'loadOnDemand']);
727            var dynamicDependencies = ['ui.bootstrap.contextMenu', 'cp.ngConfirm'] //will be added if the js is loaded from input config
728            $.each(dynamicDependencies, function (i, dep) {
729                try {
730                    if (angular.module(dep))
731                        app.requires.push(dep);
732                } catch (e) { }
733            });
734            app.run(["$http", function ($http) {
735                $http.defaults.headers.common["DNNSF-Time-Offset"] = timeZoneOffset;
736            }]);
737            app.controller('ActionFormCtrl', ActionFormCtrl);
738
739            app.directive('hasRepeaters', [function () {
740                return {
741                    restrict: 'A',
742                    priority: Number.MIN_SAFE_INTEGER,
743                    scope: false,
744                    link: function (scope) {
745                        setTimeout(function () {
746                            if (!scope.$root.repeaters) {
747                                options.showLoading && $('#' + options.rootElementClientId + ' > .common-loading-container').hide();
748                                options.tabsProLoading && dnnsf.events.broadcast('loadForm', { 'loading': false, moduleId: scope.settings.ModuleId });
749                            }
750                        }, 0)
751                    }
752                }
753            }]);
754
755            app.directive('addCustomAttributes', [function () {
756                return {
757                    restrict: 'A',
758                    scope: false,
759                    link: function (scope, element, attrs) {
760                        if (scope.settings.Fields[attrs.afField]) {
761                            var customAttributesList = scope.settings.Fields[attrs.afField].CustomAttributes;
762                            customAttributesList.forEach(function (attribute) {
763                                element.attr(attribute.name, attribute.value);
764                            });
765                        }
766                    }
767                }
768            }]);
769
770            app.directive('floatingLabel', ['$timeout', function ($timeout) {
771                return {
772                    restrict: 'A',
773                    // this directive works only with scope:false
774                    scope: false,
775                    link: function (scope, element) {
776                        if (!scope.floatLabels) {
777                            scope.floatLabels = {};
778                        }
779
780                        if (!scope.floatLabels[element.attr('id')]) {
781                            scope.floatLabels[element.attr('id')] = new FloatLabels(element.closest('.form-group')[0]);
782                        } else {
783                            scope.floatLabels[element.attr('id')].rebuild();
784                        }
785                    }
786                }
787            }]);
788
789            app.directive('repeatDone', [function () {
790                return {
791                    restrict: 'AE',
792                    scope: false,
793                    link: {
794                        pre: function (scope, element, attrs) {
795                            scope.$root.repeaters = true;
796                        },
797                        post: function (scope, element, attrs) {
798                            setTimeout(function () {
799                                if (scope.$last) { // all are rendered
800                                    options.showLoading && $('#' + options.rootElementClientId + ' > .common-loading-container').hide();
801                                    options.tabsProLoading && dnnsf.events.broadcast('loadForm', { 'loading': false, moduleId: scope.settings.ModuleId });
802                                }
803                            }, 0);
804                        }
805                    }
806                }
807            }]);
808
809            app.directive('maxSelection', [function () {
810                return {
811                    restrict: 'AE',
812                    scope: {
813                        value: '=',
814                        maxSelection: '=',
815                        ngModel: '='
816                    },
817                    link: {
818                        post: function (scope, element, attrs) {
819                            scope.$watch('value', function (newValue, oldValue) {
820                                if ((newValue !== oldValue && newValue.length === scope.maxSelection) || scope.maxSelection <= 0) {
821                                    element.attr('disabled', !scope.ngModel);
822                                } else {
823                                    element.attr('disabled', false);
824                                }
825                            });
826                        }
827                    }
828                }
829            }]);
830
831            app.factory('dataSources', ['$http', 'dnnsf', function ($http, dnnsf) {
832                return {
833                    get: function (settings, fnReady) {
834                        var p = $.extend({}, settings, { Method: 'GetData', tabId: options.tabId, mid: options.moduleId, alias: options.alias });
835                        $http({
836                            method: 'GET',
837                            url: options.adminApiUrl + '?' + $.param(p),
838                            cache: true
839                        }).success(function (data, status) {
840                            fnReady && fnReady(data);
841                        });
842                    }
843                };
844            }]);
845
846
847            // the default cookieStore does not support path or expiration
848            app.provider('$cookieStore', [function () {
849                var self = this;
850                self.defaultOptions = {};
851
852                self.setDefaultOptions = function (options) {
853                    self.defaultOptions = options;
854                };
855
856                self.$get = function () {
857                    return {
858                        get: function (name) {
859                            var jsonCookie = $.cookie(name);
860                            if (jsonCookie) {
861                                return angular.fromJson(jsonCookie);
862                            }
863                        },
864                        put: function (name, value, options) {
865                            options = $.extend({}, self.defaultOptions, options);
866                            $.cookie(name, angular.toJson(value), options);
867                        },
868                        remove: function (name, options) {
869                            options = $.extend({}, self.defaultOptions, options);
870                            $.removeCookie(name, options);
871                        }
872                    };
873                };
874            }]);
875
876            app.directive('dropdownWatch', ['$interval', function ($interval) { // resize "dropdown with checkboxes" based on content width and height
877                return {
878                    restrict: 'A',
879                    scope: {
880                        dropdownName: '=',
881                        disableCheckboxes: '@'
882                    },
883                    controller: ['$scope', '$element', function ($scope, $element) {
884                        $scope.dropdownName.show = false;
885                        var dropdownPanel = $element.parent().parent().next().children('.panel');
886                        var disabled = $element[0].disabled;
887
888                        if (disabled && $scope.disableCheckboxes === "True") {
889                            $element.prop("disabled", false);
890                        }
891                        $scope.$watch("dropdownName.show", function (newVal, oldVal) {
892                            if (!newVal)
893                                return;
894                            if (disabled && $scope.disableCheckboxes === "True") {
895                                dropdownPanel.find('a')
896                                    .parent().addClass("dnnsf-disabled-checkboxes");//  disable 'select all' and 'clear all'
897
898                                dropdownPanel.find('.normalCheckBox').each(function () {
899                                    $(this).prop("disabled", true).addClass("disabled")
900                                        .parent().addClass("not-allowed");
901                                });
902                            }
903
904                            var dropdownOpen = $interval(function () {
905                                if (dropdownPanel.width() > 0) {
906                                    resizeDropdown(dropdownPanel, $scope.dropdownName);
907                                    $interval.cancel(dropdownOpen);
908                                }
909                            }, 50); //interval
910                        });
911
912                        function resizeDropdown(dropdownPanel, dropdownName) {
913                            if (dropdownPanel.attr('data-window-width') == $(window).width())
914                                return;
915                            if (dropdownName.show) {
916                                dropdownPanel.css('opacity', '0');
917                                if (dropdownPanel.attr('data-window-width')) {
918                                    dropdownPanel.width(dropdownPanel.width() - 15); // this eliminates endless adding of padding when you resize the window
919                                    dropdownPanel.attr('style', '');
920                                }
921                                dropdownPanel.css('display', 'table');
922                                var inputWidth = dropdownPanel.parent().prev().children().first().width();
923                                var contentWidth = dropdownPanel.width() + 15; // magic number for padding
924                                var rect = dropdownPanel[0].getBoundingClientRect();
925                                var windowWidth = $(window).width();
926
927                                dropdownPanel.attr('data-window-width', windowWidth);
928                                dropdownPanel.css('display', 'inline-block'); // i took the width from display:table 
929                                // and make it inline-block with that width
930                                if (inputWidth > contentWidth) { // if the input is bigger than the dropdown panel make the panel input-sized
931                                    dropdownPanel.width(inputWidth);
932                                    dropdownPanel.parent().width(inputWidth);
933                                    dropdownPanel.css('opacity', '1');
934                                }
935                                else {
936                                    if (rect.right > windowWidth) { // if the dropdown is bigger than the window
937                                        contentWidth = contentWidth - (rect.right - windowWidth) - 15; // magic number for padding
938                                        dropdownPanel.css('overflow-x', 'scroll');
939                                    }
940                                    dropdownPanel.width(contentWidth);
941                                    dropdownPanel.parent().width(contentWidth);
942                                    dropdownPanel.css('opacity', '1');
943                                }
944                            }// show
945                        }; //resize
946                    }] //controller
947                }; //directive return
948            }]); //directive
949
950            app.directive('maxNumber', [function () {
951                return {
952                    restrict: 'A',
953                    require: 'ngModel',
954                    link: function (scope, element, attrs, ngModel) {
955                        if (typeof parseFloat(attrs.maxNumber) == 'number') {
956                            scope.$watch(attrs.ngModel, function (newVal, oldVal) {
957                                if (newVal > parseFloat(attrs.maxNumber)) {
958                                    ngModel.$setViewValue(parseFloat(attrs.maxNumber));
959                                    ngModel.$render();
960                                    $(element).closest('.form-group').addClass('has-error');
961                                    setTimeout(function () {
962                                        $(element).closest('.form-group').removeClass('has-error');
963                                    }, 1000);
964                                }
965                            })
966                        }
967                    }
968                }
969            }]);
970            // wrapper for masked input
971            app.directive('inputMask', function () {
972                return {
973                    restrict: 'A',
974                    link: function (scope, el, attrs) {
975                        delete $.jMaskGlobals.translation['#'];
976                        var label = attrs.placeholder || "";
977                        var mask = attrs.inputMask.replace(/[0-9a-zA-Z]/g, '_');
978                        var maskOptionsObject = {
979                            translation: {
980                                '9': {
981                                    pattern: /\d/, optional: true
982                                },
983                                'a': {
984                                    pattern: /[a-zA-Z]/
985                                },
986                                '*': {
987                                    pattern: /[0-9a-zA-Z]/
988                                }
989                            },
990                            placeholder: label || mask
991                        };
992
993                        var parsedMaskOptions;
994
995                        if (attrs.maskOptions) {
996                            try {
997                                parsedMaskOptions = JSON.parse(attrs.maskOptions);
998                            } catch (error) {
999                                console.error("Couldn't parse JSON in " + attrs.afFieldTitle + " Mask Options")
1000                            }
1001                        } else {
1002                            parsedMaskOptions = {}
1003                        }
1004
1005                        $(el).mask(attrs.inputMask, $.extend(maskOptionsObject, parsedMaskOptions));
1006
1007                        el.on('focus', function () {
1008                            el.attr('placeholder', mask);
1009                        })
1010                        el.on('keyup blur', function () {
1011                            label && el.attr('placeholder', label);
1012                            el.trigger('change');
1013                        });
1014                        if (scope.form.fields[attrs.afField].value) {
1015                            setTimeout(function () {
1016                                el.trigger('input');
1017                            }, 0);
1018                        }
1019                    }
1020                };
1021            });
1022
1023            // wrapper for masked input
1024            app.directive('onblur', function () {
1025                return {
1026                    restrict: 'A',
1027                    scope: {
1028                        onblur: '&'
1029                    },
1030                    link: function (scope, el, attrs) {
1031                        $(el).parents('.element-area:first').click(function (e) {
1032                            e.stopPropagation();
1033                        });
1034                        $(document).click(function () {
1035                            scope.onblur();
1036                            scope.$apply();
1037                        });
1038                    }
1039                };
1040            });
1041
1042            // DOM wathcher
1043            app.directive('domWatch', function () {
1044                return {
1045                    restrict: 'A',
1046                    link: function (scope, el, attrs) {
1047
1048                        $(el).on(attrs.domWatch, function () {
1049                            scope.$eval(attrs.ngModel + "='" + el.val() + "'");
1050                            scope.$apply();
1051                        });
1052                    }
1053                };
1054            });
1055
1056            // this directive knows to bind a value to a html control, but only when this value exists
1057            app.directive('afBindvalue', ['$compile', '$timeout', '$parse', function ($compile, $timeout, $parse) {
1058                return {
1059                    restrict: 'A',
1060                    scope: false,
1061                    require: 'ngModel',
1062                    link: function (scope, element, attrs, ngModel) {
1063                        var options = null;
1064                        if (attrs.afBindfrom)
1065                            options = scope.$eval(attrs.afBindfrom);
1066
1067                        // if it's not an input element, define a new render function
1068                        if (element.filter(':input').length == 0) {
1069                            ngModel.$render = function () {
1070                                if (ngModel.$viewValue === undefined || ngModel.$viewValue === null)
1071                                    return;
1072                                if (!element.hasClass('model-only'))
1073                                    element.html(ngModel.$viewValue);
1074                            };
1075                        }
1076
1077                        scope.$watch(attrs.afBindvalue, function (value) {
1078                            if (!scope.form.fields || !scope.form.fields[attrs.afField])
1079                                return;
1080
1081                            var field = scope.form.fields[attrs.afField];
1082                            if (field.touched)
1083                                return;
1084
1085                            if (attrs.afBindfrom) {
1086                                var options = scope.$eval(attrs.afBindfrom);
1087                                if (!options)
1088                                    return;
1089                                var optionsFound = $.grep(options, function (o) { return o.value === value });
1090                                if (optionsFound.length > 0) {
1091                                    ngModel.$setViewValue(optionsFound[
10910]);
1092                                };
1093                            }
1094                            else {
1095                                ngModel.$setViewValue(value);
1096                            }
1097                            ngModel.$render();
1098                        });
1099                    }
1100                };
1101            }]);
1102
1103            // initialize rich edits
1104            app.directive('afRichedit', ['$compile', '$timeout', '$parse', function ($compile, $timeout, $parse) {
1105                return {
1106                    require: 'ngModel',
1107                    link: function (scope, elm, attrs, ngModel) {
1108                        var fnInitRichEdit = function () {
1109
1110                            if (!$(elm).is(':visible')) {
1111                                $timeout(fnInitRichEdit, 200);
1112                                return;
1113                            }
1114
1115                            $(elm).wysiwyg({
1116                                autoGrow: false,
1117                                maxHeight: 600,
1118                                initialMinHeight: 50,
1119                                initialContent: '',
1120                                brIE: false,
1121                                replaceDivWithP: true,
1122                                events: {
1123                                    save: function () {
1124                                        try {
1125                                            ngModel.$setViewValue(this.getContent());
1126                                        } catch (e) {
1127                                        }
1128                                    }
1129                                }
1130                            });
1131
1132                            // localize wysiwyg
1133                            $('.wysiwyg [role="menuitem"]').each(function () {
1134                                var l = localization['wysiwyg.' + $(this).attr('class')];
1135                                l && $(this).attr('title', l);
1136                            });
1137
1138
1139                            ngModel.$render = function () {
1140                                $(elm).wysiwyg('setContent', ngModel.$viewValue || '');
1141                            };
1142                            $(elm).width('100%');
1143                        };
1144
1145                        $timeout(fnInitRichEdit, 100);
1146
1147                    }
1148                };
1149            }]);
1150
1151            // dnnsfAngularLock is just a quick hack to skip angualr initialization in some situations
1152            !window.dnnsfAngularLock && angular.bootstrap(formRoot, ['ActionForm' + formRoot.attr('id')]);
1153            var $_scope = angular.element(formRoot).scope();
1154            //formRoot[0].onFormSubmit = $_scope._registerToEvent['submit'];
1155
1156            // --------------------------------------------------------------------------------------------------------------------------------
1157            // Start jquery plugin e
1158            $.fn.popover && formRoot.find('span.popupOnHover').popover({ trigger: 'hover' });
1159
1160            function findErrorElement(sourceElement, errorElementClass) {
1161                var errorElement = sourceElement.find(errorElementClass);
1162
1163                if (!errorElement.length)
1164                    errorElement = sourceElement.closest('.field-container').find(errorElementClass);
1165
1166                if (!errorElement.length) {
1167                    errorElement = sourceElement.closest('.field-container').siblings(errorElementClass);
1168                }
1169
1170                return errorElement;
1171            }
1172
1173            // setup validation plugin
1174            var validationSettings = {
1175                errorElement: 'span',
1176                errorClass: 'text-danger',
1177                highlight: function (element, errorClass) {
1178                    $(element).parents('.field-container:first').addClass('has-error');
1179                },
1180                unhighlight: function (element, errorClass) {
1181                    if ($(element).hasClass('ignore')) {
1182                        return;
1183                    }
1184                    $(element).removeAttr('aria-describedby');
1185
1186                    var fieldsGroup = element.attributes['class'] ? element.attributes['class'].value.match(/(group\d+-AtLeastOneIsFilled)/) : '',
1187                        AtLeastOneIsFilled = fieldsGroup ? $(element).closest('.form-root').find('.form-group .' + fieldsGroup[0] + ':not(.required)') : '';
1188                    if (AtLeastOneIsFilled.length && !$(element).hasClass('required')) {
1189                        $.each(AtLeastOneIsFilled, function (index, input) {
1190                            if (input.type != 'checkbox' && input.type != 'radio') {
1191                                $(input).parent().removeClass('has-error');
1192                            } else {
1193                                $(input).closest('.form-group div.has-error').removeClass('has-error');
1194                            }
1195                        });
1196                        $(element).next('.text-danger').hide();
1197                    }
1198                    else { $(element).parents('.field-container:first').removeClass('has-error').find('.text-danger').hide(); }
1199                },
1200                errorPlacement: function (error, element) {
1201                    // accessibility
1202                    var elementId = element.attr('id') || element.closest('.field-container').find('[data-ng-model]').attr('id')// file-upload;
1203                    error.attr({ 'id': 'error-' + elementId, 'role': 'alert' });
1204                    element.attr('aria-describedby', 'error-' + elementId);
1205                    if (element.hasClass('multiple-choice-checkbox')) {
1206                        var parentElement = element.closest('.checkbox-list');
1207                        error.attr('id', 'error-' + parentElement.attr('id'));
1208                        element.attr('aria-describedby', 'error-' + parentElement.attr('id'));
1209                    }
1210                    // end accessibility
1211
1212                    var errPlace = findErrorElement(element, '.err-placeholder');
1213                    if (errPlace.length) {
1214                        if (errPlace.find('span.text-danger').text() != error.text())
1215                            errPlace.append(error);
1216                    } else {
1217                        if (element.is(':checkbox') || element.is(':radio')) {
1218                            element.parent().append(error);
1219                        } else {
1220                            element.next().is('.text-danger') ? element.next().replaceWith(error) : error.insertAfter(element.filter(function () {
1221                                return !element.closest('.field-container').hasClass('ng-hide')
1222                            }));
1223                        }
1224                        var tabParent = element.closest('.tab-pane');
1225                        tabParent.length && !tabParent.hasClass('active') && $('[href="#' + tabParent.attr('id') + '"]').addClass('has-error');
1226                    }
1227                },
1228                success: function (element) {
1229                    element.closest('.field-container').addClass("is-valid")
1230                },
1231                onkeyup: function (element) { return true },
1232                // numInput is for flatpickr
1233                ignore: '.ignore,:hidden,:disabled,.flatpickr-time .numInput',
1234            }
1235
1236            if (options.onFocusoutValidation) {
1237                _.assign(validationSettings, {
1238                    onfocusout: function (element) {
1239                        if ($(element).hasClass('ignore-focusout-validation'))
1240                            return true;
1241                        $(element).valid();
1242                    }, onkeyup: $.noop
1243                }); // for 'true' you need a function, 'false' works}
1244            } else {
1245                _.assign(validationSettings, { onkeyup: function (element) { $(element).valid(); } }); // for 'true' you need a function, 'false' works}
1246            }
1247
1248            formRoot.validate && formRoot.validate(validationSettings);
1249
1250            // validate required upload files
1251            $.validator && $.validator.addMethod("required-file", function (value, element) {
1252                return value !== "" || $(element).scope().queue.length || $(element).scope().loadedFiles.length;
1253            });
1254
1255            // validate required upload files
1256            $.validator && $.validator.addMethod("required-fromclass", function (value, element) {
1257                return $(element).hasClass('afvalid');
1258            });
1259
1260            $.validator && $.validator.addMethod("required-cblist", function (value, element) {
1261                var group = $(element).attr('data-validation-group');
1262                var valid = false;
1263                $('[data-validation-group="' + group + '"]').each(function () {
1264                    if (this.checked)
1265                        valid = true;
1266                });
1267                return valid;
1268            }, localization.validation.required);
1269
1270            $.validator && $.validator.addMethod("required-ddwithcb", function (value, element) {
1271                var cboxes = $(element).closest('.field-container').find(':checkbox');
1272                var valid = false;
1273                $.each(cboxes, function (i, v) {
1274                    if ($(v).is(':checked'))
1275                        valid = true;
1276                });
1277                return valid;
1278            }, localization.validation.required);
1279
1280            $.validator && $.validator.addMethod("required-dnnsf", function (value, element) {
1281
1282                if (element.nodeName.toLowerCase() === "select") {
1283                    // could be an array for select-multiple or a string, both are fine this way
1284                    var val = $(element).val();
1285                    return val && val.length > 0;
1286                }
1287                if (this.checkable(element)) {
1288                    return this.getLength(value, element) > 0;
1289                }
1290
1291                if ($_scope.form.RequiredFieldAllowsWhiteSpace) {
1292                    return value.length > 0;
1293                }
1294                else {
1295                    return $.trim(value).length > 0;
1296                }
1297            }, localization.validation.required);
1298
1299            // initialize password confirm
1300            formRoot.find('[data-password-confirm]').each(function () {
1301                $(this).rules("add", {
1302                    equalTo: '#' + $(this).attr('data-password-confirm'),
1303                    messages: {
1304                        equalTo: localization['validation.passwordNoMatch']
1305                    }
1306                });
1307            });
1308
1309            formRoot.find('[data-textbox-confirm]').each(function () {
1310                var firstField = $(this).attr('af-field-title');
1311                var secondField = $('#' + $(this).attr('data-textbox-confirm')).attr('af-field-title');
1312                $(this).rules("add", {
1313                    equalTo: '#' + $(this).attr('data-textbox-confirm'),
1314                    messages: {
1315                        equalTo: firstField + ' & ' + secondField + ' ' + localization['validation.fieldsDoNotMatch']
1316                    }
1317                });
1318            });
1319
1320            // fix width when printing
1321            if (formRoot.closest('#Table1').length) {
1322                formRoot.closest('#Table1').addClass('container')
1323                    .parent().addClass(formRoot.attr('data-rootclass'));
1324                $('body').addClass('bstrap30 bstrap3-material');
1325            }
1326
1327            //fix for when in pop-up
1328            if (formRoot.closest('.container').length == 0) {
1329                formRoot.closest('.phFormTemplate').addClass('container');
1330            }
1331
1332            function parseVar(strVar) {
1333                if (!isNaN(parseInt(strVar)))
1334                    return parseInt(strVar);
1335
1336                if (!isNaN(parseFloat(strVar)))
1337                    return parseFloat(strVar);
1338
1339                if (strVar[0] == '[') {
1340                    // looks like an array
1341                    return eval(strVar.replace('\n', ''));
1342                }
1343
1344                if (strVar == "false")
1345                    return false;
1346
1347                if (strVar == "true")
1348                    return true;
1349
1350                // just return it as string
1351                return strVar;
1352            }
1353
1354            // load localized error messages
1355            for (var key in localization) {
1356                if (key.indexOf('validation.') == 0) {
1357                    var relKey = key.substr('validation.'.length);
1358                    if ($.validator)
1359                        $.validator.messages[relKey] = localization[key].indexOf('{0}') == -1 ? localization[key] : $.validator.format(localization[key]);
1360                } else if (key.indexOf('jquery.datepicker.') == 0 && $.datepicker) {
1361                    var relKey = key.substr('jquery.datepicker.'.length);
1362                    var s = {};
1363                    s[relKey] = parseVar(localization[key]);
1364                    $.datepicker.setDefaults(s);
1365                }
1366            }
1367
1368            //// load strings from the server
1369            //$.getJSON(formRoot.attr("")
1370
1371            // fix for datepicker, onchange does not trigger validation, so call onkeyup manually
1372            formRoot.find('.datepicker').change(function () {
1373                $(this).keyup();
1374            });
1375
1376            $('.modal').on('shown.bs.modal', function () {
1377                var x = 0;
1378
1379                var checkModals = setInterval(function () {
1380                    $('.modal:visible').each(function () {
1381                        var popup = $(this);
1382                        popup.find('.modal-dialog:first').css('z-index', popup.find('.modal-backdrop:first').css('z-index') + 1);
1383                        popup.after(popup.find('.modal-dialog:first').siblings('.modal-backdrop'));
1384                    });
1385                    if (++x === 5) {
1386                        window.clearInterval(checkModals);
1387                    }
1388                }, 1000);
1389            });
1390
1391            //// initialize input masks
1392            //formRoot.find('[data-mask]').each(function () {
1393            //    $(this).mask($(this).attr('data-mask'));
1394            //});
1395
1396
1397            // intialize date pickers
1398            formRoot.find(".datepicker").each(function () {
1399                var opts = {
1400                    dateFormat: $(this).attr('data-dateformat'),
1401                    changeMonth: $(this).attr('data-changemonth') == 'true',
1402                    changeYear: $(this).attr('data-changeyear') == 'true',
1403                    //Fix of IE
1404                    fixFocusIE: false,
1405                    onSelect: function (dateText, inst) {
1406                        this.fixFocusIE = true;
1407                    },
1408                    onClose: function (dateText, inst) {
1409                        this.fixFocusIE = true;
1410                        inst.input.trigger('change');
1411                    },
1412                    beforeShow: function (input, inst) {
1413                        var result = true;
1414                        this.fixFocusIE = false;
1415                        return result;
1416                    }
1417                };
1418
1419                if ($(this).attr('data-yearrange'))
1420                    opts["yearRange"] = $(this).attr('data-yearrange');
1421
1422
1423                // merge other options
1424                if ($(this).attr('data-opts')) {
1425                    opts = $.extend(opts, eval('(' + $(this).attr('data-opts') + ')'));
1426                }
1427
1428                $(this).datepicker(opts);
1429                var theme = $(this).attr('data-theme');
1430                $('#ui-datepicker-div').each(function () {
1431                    if ($(this).parent("." + theme).size() == 0)
1432                        $(this).wrap('<div class="' + theme + '"></div>');
1433                });
1434            });
1435
1436            // init file upload
1437            formRoot.find('.file-upload').each(function () {
1438                this["aform"] = formRoot;
1439            });
1440
1441            if (window.aform_incFileUplad) {
1442                if (!$().fileupload)
1443                    return;
1444
1445                formRoot.find('.file-upload').each(function () {
1446
1447                    if (!this.aform)
1448                        return; // not one of our fields
1449
1450                    var $root = $(this).parents('.fileupload-root:first');
1451
1452                    // redefined formRoot in this context
1453                    var formRoot = this.aform;
1454                    var _this = $(this);
1455                    $root.find('.files').empty().append($('<p/>').text(angular.element(formRoot).scope().form.fields[_this.attr('data-af-field')].value));
1456
1457                    // hack for DNN 7 to leave our upload field alone
1458                    var btn = $root.find('.fileinput-button');
1459                    if (btn.find('.dnnInputFileWrapper').size() > 0) {
1460                        btn.find('input').appendTo(btn);
1461                        btn.find('.dnnInputFileWrapper').remove();
1462                    } else {
1463                        if (btn.find('input')[0])
1464                            btn.find('input')[0].wrapper = 'hack';
1465                    }
1466
1467                })
1468
1469                // once is enough
1470                window.aform_incFileUplad = false;
1471            }
1472
1473            //This is a fix for Xcilion skin for stoping changing the portal on EnterKey
1474            $(document).on('keydown', 'input:text.preventdefault', function (evt) {
1475                if (evt.keyCode == 13) {
1476                    evt.preventDefault();
1477                    evt.stopImmediatePropagation();
1478                }
1479            });
1480
1481            $('body').on('keydown', 'input:text:not(.preventdefault),input:password:not(.preventdefault)', function (evt) {
1482                var btn = $(evt.currentTarget).closest('.form-root').find('.submit[data-default-button=on]:first');
1483                if (evt.keyCode == 13 && btn.length) {
1484                    btn.click();
1485                    evt.preventDefault();
1486                }
1487            });
1488
1489            // reset buttons, sometimes firefox leaves them disabled after refresh
1490            try { formRoot.find('.button').button('reset'); } catch (e) { }
1491
1492            formRoot.on('click', ".form-button", function () {
1493                submitForm(this);
1494            });
1495
1496            function submitForm(el, fnDone, qs) {
1497                var fieldsToIgnoreSelectors = ':disabled, .ignore';// ignore dropdown with checkboxes 
1498                $_scope.fieldsToIgnoreClass = '';
1499
1500                if ($_scope.settings.SubmitHiddenFields.Value) {
1501                    fieldsToIgnoreSelectors += ',.ignore-submit-hidden-fields, .richedit'
1502                } else {
1503                    fieldsToIgnoreSelectors += ',:hidden';
1504                    $_scope.fieldsToIgnoreClass = '.ng-hide';
1505                };
1506
1507                var connectedForms = {};
1508                var btnSettings = $_scope.settings.Fields[$(el).attr('data-name')];
1509                if (!btnSettings) { //submit from tabsPro event
1510                    btnSettings = {
1511                        'isConnected': false
1512                    }
1513                }
1514                else {
1515                    var tokenizedConnectedForms = $_scope.form.fields[btnSettings.TitleCompacted].connectedForms;
1516                    btnSettings.isConnected = tokenizedConnectedForms && tokenizedConnectedForms.length > 0;
1517                    btnSettings.isConnected && (connectedForms = tokenizedConnectedForms);
1518                }
1519                var _this = el;
1520                // reset
1521                formRoot.find(".server-error").html("").hide();
1522                var causesValidation = $(_this).attr('data-validation') == 'on';
1523                var fieldsToValidate = formRoot.find('input,textarea,select,.checkbox-list').not(fieldsToIgnoreSelectors);
1524                $.each(connectedForms, function (i, v) {
1525                    var formControls = {};
1526                    formControls.fields = angular.element('#dnn' + v.FormId + 'root').scope().controls;
1527                    if (formControls.fields.length)
1528                        $_scope.controls = _.uniqBy($_scope.controls.concat(formControls || []), 'field')
1529                    // formControls.fields.length && $_scope.controls.push(formControls);
1530                    var formEl = $('#dnn' + v.FormId + 'root');
1531                    var fields = formEl.find('.field-container').not($_scope.fieldsToIgnoreClass).find('input,textarea,select,.checkbox-list').not(fieldsToIgnoreSelectors);
1532                    causesValidation && formEl.is(':visible') && fields.valid();
1533                });
1534                if (causesValidation) {
1535                    fieldsToValidate.each(function (index, input) {
1536                        if (!$(input).attr('keyup-listener')) { // we don't need duplicate listeners
1537                            $(input).attr('keyup-listener', 'true')
1538                            $(input).on("keyup change", function (event) { // add an event listener because the plugin doesn't work as it should
1539                                $(event.target).valid();
1540                            });
1541                        }
1542                    });
1543                }
1544                if (causesValidation && fieldsToValidate.size() && !fieldsToValidate.valid()) {
1545                    refreshCaptchaOnError($_scope);
1546
1547                    formRoot.find('.has-error:first').find('input,textarea,select').focus();
1548                    $('.has-error').first()[0].scrollIntoView({ behavior: "smooth", block: "center" });
1549                    fnDone && fnDone({ value: false, refresh: angular.element(formRoot).scope().settings.TabsPro_RefreshTabStateOnLeave.Value });
1550                    return false;
1551                }
1552
1553                if (_this.preventSubmit)
1554                    return;
1555
1556                // check if we need to start uploads
1557                //formRoot[0].toUpload = 0;
1558                formRoot[0].$btn = $(_this);
1559                if (qs && !$.isEmptyObject(qs)) {
1560                    formRoot[0].qs = qs;
1561                }
1562                formRoot.bind('fileuploadsubmit', function (e, data) {
1563                    // Sending the all form fields in fileUpload request
1564                    var currentData = getFormData(formRoot);
1565                    data.formData = currentData;
1566                });
1567
1568                var $btn = $(_this);
1569                $($btn).data('connectedForms', connectedForms);
1570                try { $btn.hasClass('af-btn-loading') && $btn.button('loading'); } catch (e) { }
1571
1572                $btn.data().tabEvent && $btn.data('tabEvent', false);
1573
1574                var abortSubmit = false;
1575                var waitFor = 0;
1576
1577                $.each($_scope.controls, function (i, control) {
1578                    if (control.fields && control.fields.length) {
1579                        $.each(control.fields, function (i, v) {
1580                            if (v.onSubmit)
1581                                waitFor++;
1582                        })
1583                    }
1584                    else {
1585                        if (control.onSubmit)
1586                            waitFor++;
1587                    }
1588                });
1589
1590                if (formRoot[0].onFormSubmit.length)
1591                    for (var i in formRoot[0].onFormSubmit)
1592                        formRoot[0].onFormSubmit[i]($btn);
1593                if (!waitFor) {
1594                    formRoot[0].submitData($btn, fnDone, btnSettings);
1595                }
1596                else if (waitFor && (!formRoot.find(".table-striped.files tr.file-table").length && !formRoot.find("[submit-data]").length && !btnSettings.isConnected)) {
1597                    //sending with no file uploaded
1598                    return formRoot[0].submitData($btn, fnDone, btnSettings);
1599                } else {
1600                    var submitControl = function (control) {
1601                        if (!control.onSubmit)
1602                            return
1603                        control.onSubmit(function () {
1604                            if (abortSubmit)
1605                                return;
1606
1607                            waitFor--;
1608                            if (waitFor == 0)
1609                                formRoot[0].submitData($btn, fnDone, btnSettings);
1610                        }, function (error) {
1611                            if (abortSubmit)
1612                                return;
1613                            formRoot.find(".server-error").html(error).show();
1614                            afResetButton(formRoot, $btn);
1615                            abortSubmit = true;
1616                        });
1617                    }
1618
1619                    $.each($_scope.controls, function (i, control) {
1620                        if (control.fields && control.fields.length) {
1621                            $.each(control.fields, function (i, v) {
1622                                submitControl(v);
1623                            })
1624                        }
1625                        else
1626                            submitControl(control);
1627                    });
1628                }
1629            }
1630
1631            formRoot[0].submitData = function ($btn, fnDone, btnSettings) {
1632                isConnected = btnSettings.isConnected;
1633                if (formRoot[0].qs && !$.isEmptyObject(formRoot[0].qs)) {
1634                    var submitUrl = dnnsf.getUrlParts($btn.attr('data-submiturl'))
1635                    submitUrl.query = $.extend(submitUrl.query, formRoot[0].qs);
1636                    $btn.attr('data-submiturl', submitUrl.getUrl(submitUrl));
1637                }
1638                var list = [],
1639                    deleteUrl = "";
1640
1641                if (formRoot[0].submitting)
1642                    return;
1643
1644                //getting data for submit
1645                var data = getFormData(formRoot);
1646                if (isConnected) {
1647                    data = { '$_thisForm': data };
1648                    $.each($($btn).data('connectedForms'), function (i, v) {
1649                        var fields = {}
1650                        fields[v.FormName] = getFormData($('#dnn' + v.FormId + 'root'));
1651                        $.extend(data, fields)
1652                    });
1653                }
1654
1655                //delete files
1656                $.each($_scope.controls, function (i, control) {
1657                    if (!control.deleteFiles)
1658                        return;
1659                    list = control.deleteFiles();
1660                    deleteUrl = control.deleteUrl();
1661
1662                    if (list) {
1663                        $.each(list, function (i, filename) {
1664                            $.ajax({
1665                                headers: {
1666                                    "DNNSF-Time-Offset": timeZoneOffset,
1667                                    "RequestVerificationToken": svcFramework.getAntiForgeryValue()
1668                                },
1669                                url: deleteUrl + '&f=' + filename,
1670                                type: "post",
1671                                dataType: "json"
1672                            }).done(function (data) {
1673                                formRoot[0].submitting = false;
1674                                parseFormResponse2(formRoot, $btn, data);
1675                            });
1676                        });
1677                    }
1678                });
1679
1680                var submitFormData = function (event, onDone) {
1681                    formRoot[0].submitting = true;
1682                    var setDisableState = function () {
1683                        var btns = formRoot.find('.submit').not($btn);
1684                        $.each(btns, function (i, formButton) {
1685                            $(formButton).attr('disabled') && $(formButton).data('disabled', true);
1686                            !$btn.data().tabEvent && $(formButton).attr('disabled', 'disabled');
1687                        })
1688                    }
1689                    formRoot.find('.submit-progress').css('visibility', 'visible').stop(true, true).fadeIn();
1690                    var xhr = new XMLHttpRequest();
1691                    var executed = [];
1692                    var executedOnDone = false;
1693                    var qs = $.param({
1694                        "referrer": document.referrer,
1695                        "_url": document.URL
1696                    });
1697                    if (isConnected) {
1698                        xhr.open("POST", options.apiUrl + "/MultiForm/Submit?" + $btn.attr("data-submitquery") + '&' + qs, true);
1699                        xhr.setRequestHeader("Content-type", "application/json");
1700                    } else {
1701                        xhr.open("POST", $btn.attr("data-submiturl") + '&' + qs, true);
1702                        xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
1703                    }
1704                    xhr.setRequestHeader("DNNSF-Time-Offset", timeZoneOffset);
1705                    xhr.setRequestHeader("RequestVerificationToken", svcFramework.getAntiForgeryValue());
1706
1707                    xhr.onprogress = function () {
1708                        var response = xhr.response.split("\n");
1709                        if (response[response.length - 1] == "")
1710                            response.pop();
1711
1712                        $.each(response, function (i, v) {
1713                            if (executed.indexOf(i) != -1)
1714                                return;
1715
1716                            try {
1717                                var responseObj = JSON.parse(v);
1718                            } catch (e) {
1719                                // json is incomplete. The next flush will get the rest of it.
1720                                return;
1721                            }
1722
1723                            if (isConnected && responseObj.validationErrors) {
1724                                responseObj.connectedForms = $($btn).data('connectedForms');
1725                            }
1726
1727                            parseFormResponse2(formRoot, $btn, responseObj);
1728                            if (onDone && responseObj.validationErrors) {
1729                                onDone(responseObj);
1730                                executedOnDone = true;
1731                            }
1732                            executed.push(i);
1733                        });
1734                    };
1735                    xhr.onreadystatechange = function () {
1736
1737                        formRoot[0].submitting = false;
1738                        var response = xhr.response.split("\n");
1739
1740                        if (response[response.length - 1] == "") {
1741                            response.pop(); //on firefox sometimes the response is an empty string
1742                        }
1743                        if (xhr.readyState == 4 && response.length) {
1744                            try {
1745                                var resJson = JSON.parse(response[0]);
1746                            }
1747                            catch (e) {
1748                                parseFormResponse2(formRoot, $btn, { 'error': localization.actionErrorDefaultMessage, 'reset': true });
1749                                console.log(response);
1750                                return;
1751                            }
1752                            if (onDone) {
1753                                response = _.filter(response, function (value, key) {
1754                                    return executed.indexOf(key) == -1;
1755                                });
1756                                !executedOnDone && onDone(response.length && JSON.parse(response[0]));
1757                            }
1758                            if (isConnected)
1759                                $_scope.controls = [];
1760
1761                        }
1762                    };
1763
1764                    //xhr.addEventListener('loadend', function (event) {
1765                    //    afResetButton(formRoot, $btn);
1766                    //});
1767
1768                    var ignoreEvent = false;
1769                    if (event == 'TabsPro_OnTabLeave') {
1770                        var ignoreTabLeave = $_scope.settings.TabsPro_IgnoreTabLeaveIfNoChanges && $_scope.settings.TabsPro_IgnoreTabLeaveIfNoChanges.Value;
1771                        var formSettings = dnnsf.api.actionForm.getSettings($_scope.settings.ModuleId);
1772                        var hasChanges = formSettings.submittedData && !_.isEqual(formSettings.submittedData, data);
1773                        if (ignoreTabLeave && hasChanges == false) {
1774                            onDone({});
1775                            formRoot[0].submitting = false;
1776                            ignoreEvent = true;
1777                        }
1778                        dnnsf.api.actionForm.patchSettings($_scope.settings.ModuleId, { submittedData: data });
1779                    }
1780                    if (ignoreEvent)
1781                        return;
1782                    setDisableState();
1783                    if (isConnected)
1784                        xhr.send(JSON.stringify(data));
1785                    else
1786                        xhr.send($.param(data));
1787                }
1788
1789                var event = dnnsf.getUrlParts($btn.attr("data-submiturl")).query['event'];
1790                switch (event) {
1791                    //case 'click':
1792                    //break;
1793                    //case 'TabsPro_OnTabEnter':
1794                    //break;
1795                    case 'TabsPro_OnTabLeave':
1796                        if (!options.hasTabLeaveActions)
1797                            return fnDone({ value: true, refresh: angular.element(formRoot).scope().settings.TabsPro_RefreshTabStateOnLeave.Value });
1798                        submitFormData(event, function (data) {
1799                            if (data && data.validationErrors != undefined) {
1800                                fnDone({ value: false, refresh: angular.element(formRoot).scope().settings.TabsPro_RefreshTabStateOnLeave.Value });
1801                            } else {
1802                                if (!angular.element(formRoot).scope()) {
1803                                    var waitForInitialization = setInterval(function () {
1804                                        if (angular.element(formRoot.selector).scope()) {
1805                                            fnDone({ value: true, refresh: angular.element(formRoot.selector).scope().settings.TabsPro_RefreshTabStateOnLeave.Value });
1806                                            clearInterval(waitForInitialization);
1807                                        }
1808                                    }, 5)
1809                                } else
1810                                    fnDone({ value: true, refresh: angular.element(formRoot).scope().settings.TabsPro_RefreshTabStateOnLeave.Value });
1811
1812                            }
1813                        })
1814                        break;
1815                    default:
1816                        submitFormData(event);
1817                }
1818
1819            };
1820
1821            if (options.hasTabEnterActions || options.hasTabLeaveActions) {
1822                dnnsf.events.listen('OnTabsLeave', options.moduleId, function (data, fnDone) {
1823
1824                    var isTarget = false;
1825                    $.each(data.targetModuleList, function (index, moduleId) {
1826                        if (options.moduleId == moduleId)
1827                            isTarget = true;
1828                    })
1829                    if (isTarget && options.hasTabLeaveActions) {
1830                        var submitUrl = dnnsf.getUrlParts($_scope.form.submitUrl)
1831                        submitUrl.query = $.extend(submitUrl.query, { 'event': 'TabsPro_OnTabLeave' });
1832                        var btn = document.createElement("button");
1833                        $(btn).attr('class', 'form-button');
1834                        $(btn).attr('data-submiturl', submitUrl.getUrl(submitUrl));
1835                        if ($_scope.settings.TabsPro_IgnoreValidationOnLeave.Value) {
1836                            $(btn).attr('data-validation', 'off');
1837                        } else {
1838                            $(btn).attr('data-validation', 'on');
1839                        }
1840                        $(btn).data('tabEvent', true);
1841                        submitForm(btn, fnDone);
1842                    } else {
1843                        fnDone();
1844                    }
1845                });
1846            }
1847
1848            if (options.hasTabEnterActions) {
1849                dnnsf.events.listen('OnTabsEnter', options.moduleId, function (data, fnDone) {
1850                    if (!options.hasTabEnterActions) {
1851                        return fnDone && fnDone();
1852                    }
1853                    var newQs = { 'event': 'TabsPro_OnTabEnter' };
1854                    if (data.qs && !$.isEmptyObject(data.qs)) {
1855                        $.extend(newQs, data.qs);
1856                        dnnsf.api.actionForm.patchSettings(options.moduleId, { passQs: data.qs });
1857                    }
1858                    var isTarget = false;
1859                    $.each(data.targetModuleList, function (index, moduleId) {
1860                        if (options.moduleId == moduleId)
1861                            isTarget = true;
1862                    })
1863                    if (isTarget) {
1864                        var submitUrl = dnnsf.getUrlParts($_scope.form.submitUrl)
1865                        submitUrl.query = $.extend(submitUrl.query, newQs);
1866                        var btn = document.createElement("button");
1867                        $(btn).attr('class', 'form-button');
1868                        $(btn).attr('data-submiturl', submitUrl.getUrl(submitUrl));
1869                        $(btn).attr('data-validation', 'off');
1870                        $(btn).data('tabEvent', true);
1871                        submitForm(btn, fnDone, data.qs);
1872                    } else {
1873                        fnDone();
1874                    }
1875                });
1876            }
1877
1878            dnnsf.events.listen('ActionFormPing', options.moduleId, function (data, fnDone) {
1879                $.each(data.targetModuleList, function (index, moduleId) {
1880                    if (options.moduleId == moduleId) {
1881                        fnDone(options.moduleId);
1882                    } else {
1883                        fnDone();
1884                    }
1885                })
1886            });
1887        };
1888    }
1889
1890    function getFormData(formRoot) {
1891        var $_scope = angular.element(formRoot).scope();
1892        var data = {};
1893        formRoot.find(':input,[data-val],.value-node').not("button[class^='trumbowyg-'], textarea.g-recaptcha-response").each(function () {
1894            //formRoot.find('input').filter(':text,:password,:hidden').add(formRoot.find("select,textarea")).each(function () {
1895            if (!$(this).attr("id") || $(this).closest($_scope.fieldsToIgnoreClass).length)
1896                return;
1897            // initialize to empty
1898            var name = $(this).attr("field-id") || $(this).attr("id").replace(/dnn\d+/, "");
1899            if (!data[name])
1900                data[name] = '';
1901
1902            // for radios, only set if selected
1903            if ($(this).attr('type') == 'radio') {
1904                if (this.checked)
1905                    data[name] = $(this).val();
1906            } else if ($(this).attr('data-val') || $(this).attr('data-val') === '') {
1907                data[name] = $(this).attr('data-val');
1908            }
1909            else if ($(this).hasClass('value-node')) {
1910                data[name] = $(this).html();
1911            } else {
1912                data[name] = $(this).val();
1913            }
1914
1915        });
1916
1917        var checkboxes = _.map(formRoot.find('.field-container').not($_scope.fieldsToIgnoreClass).find('[type="checkbox"][id]:not([id=""])'),
1918            function (checkbox) {
1919                return [$(checkbox).attr('id').replace(/dnn\d+/, ''), $(checkbox)]
1920            });
1921
1922        var part_checkboxes = _.partition(checkboxes,
1923            function (checkbox) {
1924                return checkbox[0].indexOf('-') == -1
1925            });
1926
1927        _.each(part_checkboxes[0],
1928            function (checkbox) {
1929                data[checkbox[0]] = checkbox[1].is(':checked') ? 'True' : 'False'
1930            });
1931
1932        var checkbox_lists = _.groupBy(part_checkboxes[1],
1933            function (checkbox) {
1934                return checkbox[0].substr(0, checkbox[0].indexOf('-'))
1935            });
1936
1937        _.each(checkbox_lists,
1938            function (list, name) {
1939                data[name] = JSON.stringify(
1940                    _.map(
1941                        _.filter(list,
1942                            function (checkbox) {
1943                                return checkbox[1].is(':checked')
1944                            }),
1945                        function (checkbox) {
1946                            return checkbox[1].val()
1947                        }))
1948            });
1949
1950        formRoot.find(".itemwithqty input:visible").each(function () {
1951            data[$(this).attr("id").replace(/dnn\d+/, "")] = $('#' + $(this).attr("id") + 'Qty').val() + ' ' + $(this).val();
1952        });
1953
1954        formRoot.find('iframe.dnnsf-text-editor').each(function () {
1955            if (dnnsf.canAccessIFrame(this) && this.contentWindow.getContent) {
1956                data[$(this).attr("name").replace(/dnn\d+/, "")] = this.contentWindow.getContent();
1957            }
1958        });
1959
1960        // finally, call controls
1961        $.each($_scope.controls, function (i, control) {
1962            if (!control.getValue)
1963                return;
1964            data[control.field.TitleCompacted] = control.getValue();
1965        });
1966
1967        _.forEach($_scope.form.fields, function (afField) {
1968            if (afField.type === "recaptcha") {
1969                data[afField.name] = afField.value;
1970                return false;
1971            }
1972        });
1973
1974        return data;
1975    }
1976
1977    function resetRecaptchaValidation(widgetId, formRoot) {
1978
1979        var recaptchaElement = $("div[vc-recaptcha]", formRoot);
1980
1981        if (!recaptchaElement.length) {
1982            return;
1983        }
1984
1985        if (typeof grecaptcha === 'undefined') {
1986            console.error('grecaptcha is undefined');
1987            return;
1988        }
1989
1990        grecaptcha.reset(widgetId);
1991        var recaptchaParent = recaptchaElement.closest('.field-container');
1992        var recaptchaError = recaptchaParent.find('.text-danger');
1993
1994        setTimeout(function () {
1995            recaptchaParent.removeAttr('aria-describedby');
1996            recaptchaError.remove();
1997        }, 3000);
1998    }
1999
2000    function afResetButton(formRoot, $btn) {
2001        if (!$btn) {
2002            return;
2003        }
2004        setTimeout(function () {
2005            try { $btn.button('reset'); } catch (e) { }
2006            var btns = formRoot.find('.submit').not($btn);
2007            $.each(btns, function (i, formButton) {
2008                if ($(formButton).data('disabled')) {
2009                    $(formButton).data('disabled', false)
2010                } else {
2011                    $(formButton).removeAttr('disabled');
2012                }
2013            })
2014            formRoot && formRoot.find('.submit-progress').stop(true, true).fadeOut(function () {
2015                $(this).css('visibility', 'hidden');
2016            });
2017        }, 500);
2018    }
2019
2020    function refreshCaptchaOnError(scope) {
2021        formRoot.find('.imgcode').each(function (index, captchaImg) {
2022            scope.refreshCaptcha(scope.settings.ModuleId, $(captchaImg).attr('data-af-field'));
2023        });
2024    }
2025
2026    function parseFormResponse2(formRoot, $btn, data) {
2027
2028        var $ = dnnsfjQuery;
2029        parseFormResponse(data, {
2030            executeJsFunction: function (fnName) {
2031                window.parent[fnName](window.frameElement);
2032            },
2033            executeJsCode: function (jsCode) {
2034                if (!form)
2035                    var form = formRoot.scope() && formRoot.scope().form;
2036
2037                var toEval = new Function('form', jsCode);
2038
2039                // try/catch is already implemented in the code being executed that we have in Common in the actions
2040                toEval.call(this, form);
2041            },
2042            error: function (err, reset) {
2043                formRoot.find(".server-error").append($.parseHTML(err)).show();
2044                $('.g-recaptchadnnsf', formRoot).each(function (index, recaptchaElement) {
2045                    grecaptcha.reset($(this).attr('data-widgetid'));
2046                    if ($(recaptchaElement).attr('data-size') === 'invisible') {
2047                        grecaptcha.execute($(this).attr('data-widgetid'));
2048                    }
2049                });
2050            },
2051            validationErrors: function (_data) {
2052                var displayErrors = function (errors, mid) {
2053                    refreshCaptchaOnError(formRoot.scope());
2054                    $.each(errors, function (i, err) {
2055                        var fieldId = mid + err.fieldId;
2056                        var field = $('#dnn' + fieldId);
2057                        var parent = field.closest('.field-container');
2058
2059                        if (!parent.length)
2060                            parent = $('[name="dnn' + fieldId + '"]').closest('.field-container');
2061
2062                        // tabspro specific
2063                        var tabParent = parent.closest('.tab-pane');
2064                        tabParent.length && !tabParent.hasClass('active') && $('[href="#' + tabParent.attr('id') + '"]').addClass('has-error');
2065                        // end tabspro specific
2066
2067                        field.attr('aria-describedby', 'error-' + field.attr('id'));
2068
2069                        parent.addClass('has-error');
2070                        $('#dnn' + fieldId)
2071                        parent.find('.text-danger').length ?
2072                            parent.find('.text-danger').html(err.message).show() :
2073                            parent.append('<span id="' + 'error-' + field.attr('id') + '" class="text-danger">' + err.message + '</span>');
2074
2075                        var errorElements = $('.has-error');
2076                        if (errorElements.length) {
2077                            // focus and scroll to the element
2078                            errorElements.first()[0].scrollIntoView({ behavior: "smooth", block: "center" });
2079                        } else {
2080                            console.error("Action Form: Validation error on a hidden field.")
2081                        }
2082                    });
2083
2084                }
2085                var mid = $(formRoot).parent().attr('data-moduleid');
2086                if (_data.validationErrors.constructor === Array) {
2087                    //is current form
2088                    displayErrors(_data.validationErrors, mid);
2089                } else {
2090                    //conected forms
2091                    $.each(_data.validationErrors, function (i, v) {
2092                        if (v.length) {
2093                            var form;
2094
2095                            if (i != '$_thisForm') {
2096                                form = _data.connectedForms.find(function (j, k) {
2097                                    return j.FormName.toLowerCase() == i.toLowerCase();
2098                                })
2099                            }
2100                            displayErrors(v, form ? form.FormId : mid);
2101                        }
2102                    });
2103                }
2104
2105                resetRecaptchaValidation(formRoot.scope().widgetId, formRoot);
2106
2107                return;
2108            },
2109            redirect: function (url, isPushState) {
2110                if (data.forceDownload) {
2111                    var urlPath = dnnsf.getUrlParts(url).pathname;
2112                    var fileNameAndExtension = urlPath.substring(urlPath.lastIndexOf("/") + 1);
2113                    if (!window.ActiveXObject) {
2114                        var save = document.createElement('a');
2115                        save.href = url;
2116                        save.target = '_blank';
2117                        save.download = fileNameAndExtension || 'unknown';
2118
2119                        var evt = new MouseEvent('click', {
2120                            'view': window,
2121                            'bubbles': true,
2122                            'cancelable': false
2123                        });
2124                        save.dispatchEvent(evt);
2125
2126                        (window.URL || window.webkitURL).revokeObjectURL(save.href);
2127                        afResetButton(formRoot, $btn);
2128
2129                    }
2130                    // for IE < 11
2131                    else if (!!window.ActiveXObject && document.execCommand) {
2132                        var _window = window.open(url, '_blank');
2133                        _window.document.close();
2134                        _window.document.execCommand('SaveAs', true, fileNameAndExtension || url)
2135                        _window.close();
2136                        afResetButton(formRoot, $btn);
2137                    }
2138                    return;
2139                }
2140
2141                if (!data.popup && !data.newTab) {
2142
2143                    if (isPushState) {
2144                        //$('.angrid').scope().$location.url(url);
2145                        setTimeout(function () {
2146                            window.history.pushState({}, '', url + location.hash);//added location.hash for tabsPro
2147                            afResetButton(formRoot, $btn);
2148                        }, 500)
2149                    } else {
2150                        window.location = url;
2151                        if (url.indexOf("mailto:") != -1 || url.indexOf("tel:") != -1 || url.indexOf("ftp:") != -1) {
2152                            afResetButton(formRoot, $btn);
2153                        }
2154                    }
2155                    return;
2156                }
2157
2158                // handle new tab
2159                if (data.newTab) {
2160                    if (!!window.ApplePaySession) { // it's Safari 10+
2161                        window.location = url;
2162                    } else {
2163                        window.open(url, '_blank');
2164                        afResetButton(formRoot, $btn);
2165                    }
2166                    return;
2167                }
2168
2169                // open popup
2170                //set modal and append to body
2171                var popup = $('<div class="af-modal modal fade">' +
2172                    '<div class="modal-dialog modal-lg">' +
2173                    '<div class="modal-content">' +
2174                    '<div class="modal-header">' +
2175                    '<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>' +
2176                    '<h4 class="modal-title">' + data.popupTitle + '</h4>' +
2177                    '</div>' +
2178                    '<div class="modal-body">' +
2179                    '<iframe width="100%" src="' + url + '" frameborder="0" scrolling="yes"></iframe>' +
2180                    '</div>' +
2181                    '</div>' +
2182                    '</div>' +
2183                    '</div>').appendTo('body');
2184
2185                //console.log(popup.modal); // removed unnecesary logs
2186                popup.modal({
2187                    backdrop: true
2188                });
2189
2190                popup.on('hidden.bs.modal', function () {
2191                    //stop resizing
2192                    window.clearInterval(resizeInterval);
2193                    //remove from DOM
2194                    popup.remove();
2195                    //remove backdrop
2196                    $('.modal-backdrop').remove();
2197
2198                });
2199
2200                afResetButton(formRoot, $btn);
2201
2202                //resize iframe so it has no scroll bar
2203                var __prevHeight = 0;
2204                var resizeInterval = setInterval(function () {
2205                    var iframe = $('.af-modal:visible').find('iframe');
2206                    try {
2207                        var bodyHeight = iframe[0].contentWindow.document.body.scrollHeight;// the corect way to get iframe height
2208                        //if (bodyHeight < __prevHeight) {
2209                        //    bodyHeight = bodyHeight - 50;// Decreseaing in height is slow because something somewhere is intercating with them. Here is a little boost ...
2210                        //}
2211                        if (bodyHeight != __prevHeight) {
2212                            __prevHeight = bodyHeight;
2213                            iframe.height(Math.max(200, bodyHeight));// minimum 200px
2214                        }
2215                    } catch (e) {
2216                        iframe.height(window.innerHeight - 240);// better save the sorry
2217                    }
2218                }, 1000);
2219            },
2220
2221            message: function (msg, type) {
2222                if (!type || type == 'success') {
2223                    formRoot.find(".c-form").slideUp();
2224                    formRoot.find(".submit-confirm2").hide();
2225                    $('html, body').animate({
2226                        scrollTop: formRoot.offset().top - 200
2227                    }, 500);
2228                }
2229                formRoot.find(".submit-confirm").html(msg).show();
2230                var scope = formRoot.find(".submit-confirm").scope();
2231
2232                angular.element(formRoot).injector().invoke(function ($compile) {
2233                    $compile(formRoot.find(".submit-confirm").contents())(scope);
2234                });
2235
2236            },
2237            appendHtml: function (appendHtml, appendTo, reset) {
2238                $(appendTo).append(appendHtml);
2239                reset && afResetButton(formRoot, $btn);
2240            },
2241            data: function (data) {
2242                var moduleId = data.baseId.replace('dnn', '');
2243                dnnsf.api.actionForm.patchSettings(moduleId, { Data: data });
2244                angular.element(formRoot).scope().load(moduleId);
2245                angular.element(formRoot).scope().$apply();
2246                angular.element(formRoot).scope().$broadcast('updateFormData');
2247            },
2248            noOp: function () {
2249            }
2250        }, $btn, formRoot);
2251    }
2252
2253    function parseFormResponse(data, handlers, afButton, afFormRoot) {
2254
2255        var $ = dnnsfjQuery;
2256
2257        // initialize with default handlers, unless provieded by caller
2258        handlers = $.extend({
2259            keepOnPage: function (url) {
2260                window.location.reload(true);
2261            },
2262            redirect: function (url) {
2263                window.location = url;
2264            },
2265            appendHtml: function (appendHtml, appendTo) {
2266                $(appendTo).append(appendHtml);
2267            },
2268            error: function (err) { },
2269            message: function (msg, type) { },
2270            data: function (msg, type) { },
2271            executeJsCode: function (jsCode) {
2272                try {
2273                    eval(jsCode)
2274                } catch (e) {
2275                    console.error(e);
2276                }
2277            }
2278        }, handlers);
2279
2280        // parse response and call handlers
2281        if (data.functionName) {
2282            handlers.executeJsFunction && handlers.executeJsFunction(data.functionName);
2283        } if (data.JsCode) {
2284            handlers.executeJsCode && handlers.executeJsCode(data.JsCode);
2285        } else if (data.Error || data.error) {
2286            handlers.error && handlers.error(data.Error || data.error, data.reset);
2287        } else if (data.validationErrors) {
2288            handlers.validationErrors && handlers.validationErrors(data);
2289        } else if (data.Content) {
2290            handlers.message && handlers.message(data.Content, data.Type);
2291        } else if (data.KeepOnPage) {
2292            handlers.KeepOnPage && handlers.KeepOnPage(data.Url);
2293        } else if (data.Url) {
2294            handlers.redirect && handlers.redirect(data.Url, data.PushState);
2295            return; // do not reset form button by default
2296        } else if (data.appendHtml) {
2297            handlers.appendHtml && handlers.appendHtml(data.appendHtml, data.appendTo, data.reset);
2298            return;
2299        } else if (data.data) {
2300            handlers.data && handlers.data(data.data);
2301        } else if (data.noOp) {
2302            handlers.noOp && handlers.noOp();
2303        }
2304
2305        afResetButton(afFormRoot || formRoot, afButton);
2306    }
2307
2308    return {
2309        initForm: initForm,
2310        getFormData: getFormData,
2311        afResetButton: afResetButton,
2312        parseFormResponse: parseFormResponse
2313    }
2314})(dnnsfjQuery, window.dnnsfAngular15);
2315
2316var initForm = afApp.initForm,
2317    getFormData = afApp.getFormData,
2318    afResetButton = afApp.afResetButton,
2319    parseFormResponse = afApp.parseFormResponse;
2320
2321function browseGrid(settings) {
2322    $('body').append('<div class="loader-wrapper" id="modalLoader"><div class="loader"></div></div>');
2323    $.get(window.dnnsf.commonUrl + '/static/dnnsf/tpl/gridModal.html', function (data) {
2324        var iframeData = data.replace('gridUrl', settings.url);
2325        iframeData = iframeData.replace('popupHeight', $(window).height() - 150 + 'px');
2326        $('body').append(iframeData);
2327        $('#gridFrame').load(function () {
2328            $('#gridFrame').contents().find('body').css({ 'overflow': 'auto' });
2329            $('#gridFrame').contents().find('body table').css({ 'width': '90%', 'margin': '0 auto' });
2330            setTimeout(function () {
2331                dnnsfjQuery('#gridModal').modal('show');
2332                $('#modalLoader').remove();
2333            }, 500);
2334            dnnsfjQuery('#gridModal').on('shown.bs.modal', function (e) {
2335                getData();
2336            });
2337        });
2338        dnnsfjQuery('#gridModal').on('hidden.bs.modal', function (e) {
2339            $('#gridModal').remove();
2340        });
2341    });
2342    function getData() {
2343        var iframe = window.frames['gridFrame'].document;
2344        $('body', iframe).on('click', '.grid-item', function () {
2345            if (!$(this).has('span.grid-field-value').size())
2346                return;
2347            var gridScope = window.frames['gridFrame'].angular.element($(this).closest('.item-value')).scope();
2348            $.each(settings.mappings, function (e, f) {
2349                var elem = $('* [data-ng-model^="form.fields.' + e + '"]');
2350                var parent = angular.element(elem).closest('.form-root').scope();
2351                parent.form.fields[e].value = gridScope.item[f];
2352                parent.form.fields[e].onChange && parent.form.fields[e].onChange(parent.form);
2353                parent.$apply();
2354            });
2355            dnnsfjQuery('#gridModal').modal('hide');
2356        })
2357            .on('mouseenter', '.grid-item', function () {
2358                $(this).closest('.item-value').addClass('hover-item');
2359            })
2360            .on('mouseleave', '.grid-item', function () {
2361                $(this).closest('.item-value').removeClass('hover-item');
2362            });
2363    };
2364}

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.