PageSourceSearch

https://www.mynorthside.com/Scripts/Bundles/RockUi?v=isuJYDaGjIlxDLxlPTL9LaaGFwIUFAf3EjkvpBIzU2c1

js mynorthside.com collected 2026-10-01 16:12:53 UTC 436,126 bytes, 9,690 lines download raw bytes

1/* Minification failed. Returning unminified contents.
2(823,59-60): run-time error JS1195: Expected expression: >
3(824,70-71): run-time error JS1195: Expected expression: >
4(826,30-31): run-time error JS1195: Expected expression: )
5(828,26-27): run-time error JS1006: Expected ')': ;
6(831,21-22): run-time error JS1002: Syntax error: }
7(833,54-55): run-time error JS1195: Expected expression: >
8(835,25-26): run-time error JS1002: Syntax error: }
9(839,44-45): run-time error JS1195: Expected expression: )
10(839,46-47): run-time error JS1004: Expected ';': {
11(1097,14-15): run-time error JS1195: Expected expression: ,
12(1098,50-51): run-time error JS1004: Expected ';': {
13(1105,58-59): run-time error JS1195: Expected expression: >
14(1105,95-96): run-time error JS1004: Expected ';': )
15(1107,17-18): run-time error JS1002: Syntax error: }
16(1109,37-38): run-time error JS1004: Expected ';': {
17(1113,50-51): run-time error JS1195: Expected expression: >
18(1113,80-81): run-time error JS1004: Expected ';': )
19(1114,19-23): run-time error JS1034: Unmatched 'else'; no 'if' defined: else
20(1121,50-51): run-time error JS1195: Expected expression: >
21(1121,81-82): run-time error JS1004: Expected ';': )
22(1130,39-40): run-time error JS1004: Expected ';': {
23(1184,5-6): run-time error JS1002: Syntax error: }
24(1184,7-8): run-time error JS1197: Too many errors. The file might not be a JavaScript file: )
25(1183,9-23): run-time error JS1018: 'return' statement outside of function: return exports
26(1131,13,1133,61): run-time error JS1018: 'return' statement outside of function: return (s)
27                ? this.before(s).remove()
28                : $("<p>").append(this.eq(0).clone()).html()
29(834,29-71): run-time error JS1018: 'return' statement outside of function: return selectNodeIds.indexOf(node.id) >= 0
30(833,25-52): run-time error JS1018: 'return' statement outside of function: return allNodes.filter(node
31(830,25-68): run-time error JS1018: 'return' statement outside of function: return filterArray(allNodes, selectNodeIds)
32 */
33Sys.Application.add_load(function() {
34    $(function() {
35        $('[data-toggle="tooltip"]').tooltip();
36    });
37
38    stickyTableHeaderInit()
39});
40
41$(document).on('enabledStickiness.stickyTableHeaders', function(event) {
42    $(window).on('resize', function(event) {
43        stickyTableHeaderInit();
44
45        $('.table-responsive').each(stickyRepaint);
46    });
47    var offsetLeft = $(event.target).offset().left;
48    var twidth = $(event.target).parent('.table-responsive').width();
49    $(event.target)
50        .find('.tableFloatingHeaderOriginal')
51        .css({ left: offsetLeft, right: $(window).width() - offsetLeft - twidth, clip: 'rect(0px, ' + (twidth + 2) + 'px, 300px, 0px)' });
52
53    $(event.target)
54        .parent('.table-responsive')
55        .scroll(stickyRepaint);
56});
57
58function stickyTableHeaderInit() {
59    var stickyHeaders = document.querySelector('.js-sticky-headers');
60
61    if (stickyHeaders) {
62        var navbarFixedTop = document.querySelector('.navbar-fixed-top');
63
64        $(stickyHeaders).each(function() {
65            var table = $(this);
66            if (table.parents('.page-fullscreen-capable').length || table.parents('.is-fullscreen').length) {
67                table.stickyTableHeaders({scrollableArea: table.parents('.panel-block')});
68            } else {
69                if ($(navbarFixedTop).length && $(navbarFixedTop).css('position') === 'fixed') {
70                    table.stickyTableHeaders({ fixedOffset: $(navbarFixedTop) });
71                } else {
72                    table.stickyTableHeaders();
73                }
74            }
75        });
76    }
77}
78
79function stickyRepaint() {
80    var twidth = $(this).width();
81    var scroll = $(this).scrollLeft();
82    var offsetLeft = $(this).offset().left;
83    $('.tableFloatingHeaderOriginal').css({
84        left: offsetLeft - scroll,
85        clip: 'rect(0px, ' + (scroll + twidth) + 'px, 300px, ' + scroll + 'px)'
86    });
87}
88;
89(function ($) {
90    window.Rock = window.Rock || {};
91
92    Rock.dialogs = (function () {
93        var _dialogs = {},
94            exports = {
95                // Presents a bootstrap style alert box with the specified message
96                // then executes the callback function(result)
97                alert: function (msg) {
98                    bootbox.dialog({
99                        message: msg,
100                        buttons: {
101                            ok: {
102                                label: 'OK',
103                                className: 'btn-primary'
104                            }
105                        }
106                    });
107                },
108
109                // Presents a bootstrap style alert box with the specified message
110                // then executes the callback function(result)
111                confirm: function (msg, callback) {
112                    bootbox.dialog({
113                        message: msg,
114                        buttons: {
115                            ok: {
116                                label: 'OK',
117                                className: 'btn-primary',
118                                callback: function () {
119                                    callback(true);
120                                }
121                            },
122                            cancel: {
123                                label: 'Cancel',
124                                className: 'btn-default',
125                                callback: function () {
126                                    callback(false);
127                                }
128                            }
129                        }
130                    });
131                },
132
133                // Presents a bootstrap style alert box which prevents on "Cancel" and continues on "Ok"
134                confirmPreventOnCancel: function (e, msg) {
135                    // make sure the element that triggered this event isn't disabled
136                    if (e.currentTarget && e.currentTarget.disabled) {
137                        return false;
138                    }
139
140                    e.preventDefault();
141                    bootbox.dialog({
142                        message: msg,
143                        buttons: {
144                            ok: {
145                                label: 'OK',
146                                className: 'btn-primary',
147                                callback: function () {
148                                    var postbackJs = e.target.href ? e.target.href : e.target.parentElement.href;
149                                    window.location = postbackJs;
150                                }
151                            },
152                            cancel: {
153                                label: 'Cancel',
154                                className: 'btn-default'
155                            }
156                        }
157                    });
158                },
159
160                // Presents a bootstrap style alert box with a 'Are you sure you want to delete this ...' message
161                // Returns true if the user selects OK
162                confirmDelete: function (e, nameText, additionalMsg)
163                {
164                    var msg = 'Are you sure you want to delete this ' + nameText + '?';
165                    if (additionalMsg)
166                    {
167                        msg += ' ' + additionalMsg;
168                    }
169                    this.confirmPreventOnCancel(e, msg);
170                },
171
172                // Updates the modal so that scrolling works
173                updateModalScrollBar: function (controlId) {
174                    Rock.controls.modal.updateSize(controlId);
175                }
176            }
177
178        return exports;
179    }());
180}(jQuery));
181;
182(function () {
183    'use strict';
184    window.Rock = window.Rock || {};
185    Rock.settings = (function () {
186        var _settings = {},
187            exports = {
188                initialize: function (options) {
189                    if (typeof options === 'object') {
190                        _settings = options;
191                    }
192                },
193                get: function (key) {
194                    return _settings[key];
195                },
196                insert: function (key, value) {
197                    _settings[key] = value;
198                },
199                remove: function (key) {
200                    if (_settings[key]) {
201                        delete _settings[key];
202                    }
203                }
204            };
205 
206        return exports;
207    }());
208}());;
209(function () {
210    'use strict';
211    window.Rock = window.Rock || {};
212
213    Rock.utility = (function () {
214        var _utility = {},
215            exports = {
216
217                setContext: function (restController, entityId) {
218                    // Get the current block instance object
219                    $.ajax({
220                        type: 'PUT',
221                        url: Rock.settings.get('baseUrl') + 'api/' + restController + '/SetContext/' + entityId,
222                        success: function (getData, status, xhr) {
223                        },
224                        error: function (xhr, status, error) {
225                            alert(status + ' [' + error + ']: ' + xhr.responseText);
226                        }
227                    });
228                },
229                uuidv4: function () {
230                    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
231                        var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
232                        return v.toString(16);
233                    });
234                },
235                dashify: function (str) {
236                    return str.replace(/\W+/g, '-').replace(/\-$/, '').toLowerCase();
237                }
238            };
239
240        return exports;
241    }());
242}());
243;
244(function ($) {
245    'use strict';
246    window.Rock = window.Rock || {};
247    Rock.controls = Rock.controls || {};
248
249    Rock.controls.accountPicker = (function () {
250        var AccountPicker = function (options) {
251            this.options = options;
252            // set a flag so that the picker only auto-scrolls to a selected item once. This prevents it from scrolling at unwanted times
253            this.alreadyScrolledToSelected = false;
254            this.iScroll = null;
255        },
256            exports;
257
258        AccountPicker.prototype = {
259            constructor: AccountPicker,
260            initialize: function () {
261                var $control = $('#' + this.options.controlId),
262                    $tree = $control.find('.treeview'),
263                    treeOptions = {
264                        customDataItems: this.options.customDataItems,
265                        enhanceForLongLists: this.options.enhanceForLongLists,
266                        multiselect: this.options.allowMultiSelect,
267                        categorySelection: this.options.allowCategorySelection,
268                        categoryPrefix: this.options.categoryPrefix,
269                        restUrl: this.options.restUrl,
270                        searchRestUrl: this.options.searchRestUrl,
271                        restParams: this.options.restParams,
272                        expandedIds: this.options.expandedIds,
273                        expandedCategoryIds: this.options.expandedCategoryIds,
274                        showSelectChildren: this.options.showSelectChildren,
275                        id: this.options.startingId
276                    },
277
278                    // used server-side on postback to display the selected nodes
279                    $hfItemIds = $control.find('.js-item-id-value'),
280                    // used server-side on postback to display the expanded nodes
281                    $hfExpandedIds = $control.find('.js-initial-item-parent-ids-value'),
282                    $hfExpandedCategoryIds = $control.find('.js-expanded-category-ids');
283
284                // Custom mapping override to add features items
285                this.options.mapItems = function (arr, treeView) {
286
287                    return $.map(arr, function (item) {
288
289                        var node = {
290                            id: item.Guid || item.Id,
291                            name: item.Name || item.Title,
292                            iconCssClass: item.IconCssClass,
293                            parentId: item.ParentId,
294                            hasChildren: item.HasChildren,
295                            isActive: item.IsActive,
296                            countInfo: item.CountInfo,
297                            isCategory: item.IsCategory,
298                            path: item.Path,
299                            totalCount: item.TotalCount
300                        };
301
302                        // Custom node properties passed in from the *Picker.cs using the ItemPicker base CustomDataItems property
303                        if (treeView.options.customDataItems && treeView.options.customDataItems.length > 0) {
304                            treeView.options.customDataItems.forEac
304h(function (dataItem, idx) {
305                                if (!node.hasOwnProperty(dataItem)) {
306                                    node['' + dataItem.itemKey + ''] = item['' + dataItem.itemValueKey + ''];
307                                }
308                            });
309                        }
310
311                        if (node.parentId === null) {
312                            node.parentId = '0';
313                        }
314
315                        if (item.Children && typeof item.Children.length === 'number') {
316
317                            // traverse using the _mapArrayDefault in rockTree.js
318                            node.children = _mapArrayDefault(item.Children, treeView);
319                        }
320
321                        if (node.isCategory) {
322                            node.id = treeView.options.categoryPrefix + node.id;
323                        }
324
325                        return node;
326                    });
327                }
328
329                if (typeof this.options.mapItems === 'function') {
330                    treeOptions.mapping = {
331                        mapData: this.options.mapItems
332                    };
333                }
334
335                // clean up the tree (in case it was initialized already, but we are rebuilding it)
336                var rockTree = $tree.data('rockTree');
337
338                if (rockTree) {
339                    rockTree.nodes = [];
340                }
341                $tree.empty();
342
343                var $scrollContainer = $control.find('.scroll-container .viewport');
344                var $scrollIndicator = $control.find('.track');
345                this.iScroll = new IScroll($scrollContainer[0], {
346                    mouseWheel: true,
347                    indicators: {
348                        el: $scrollIndicator[0],
349                        interactive: true,
350                        resize: false,
351                        listenY: true,
352                        listenX: false,
353                    },
354                    click: false,
355                    preventDefaultException: { tagName: /.*/ }
356                });
357
358                // Since some handlers are "live" events, they need to be bound before tree is initialized
359                this.initializeEventHandlers();
360
361                if ($hfItemIds.val() && $hfItemIds.val() !== '0') {
362                    treeOptions.selectedIds = $hfItemIds.val().split(',');
363                }
364
365                if ($hfExpandedIds.val()) {
366                    treeOptions.expandedIds = $hfExpandedIds.val().split(',');
367                }
368
369                if ($hfExpandedCategoryIds.val()) {
370                    treeOptions.expandedCategoryIds = $hfExpandedCategoryIds.val().split(',');
371                }
372
373                // Initialize the rockTree and pass the tree options also makes http fetches
374                $tree.rockTree(treeOptions);
375
376                $control.find('.picker-preview').hide();
377                $control.find('.picker-treeview').hide();
378
379                if (treeOptions.allowMultiSelect) {
380                    $control.find('.picker-preview').remove();
381                    $control.find('.picker-treeview').remove();
382                }
383
384                this.updateScrollbar();
385            },
386            initializeEventHandlers: function () {
387                var self = this,
388                    $control = $('#' + this.options.controlId),
389                    $spanNames = $control.find('.selected-names'),
390                    $hfItemIds = $control.find('.js-item-id-value'),
391                    $hfExpandedIds = $control.find('.js-initial-item-parent-ids-value'),
392                    $hfItemNames = $control.find('.js-item-name-value'),
393                    $searchValueField = $control.find('.js-existing-search-value');
394
395                // Bind tree events
396                $control.find('.treeview')
397                    .on('rockTree:selected', function (e) {
398                        var rockTree = $control.find('.treeview').data('rockTree');
399
400                        if (rockTree.selectedNodes) {
401                            var ids = rockTree.selectedNodes.map(function (v) { return v.id }).join(',');
402                            $hfItemIds.val(ids);
403                        }
404
405                        self.togglePickerElements();
406                    })
407                    .on('rockTree:itemClicked', function (e, data) {
408                        // make sure it doesn't auto-scroll after something has been manually clicked
409                        self.alreadyScrolledToSelected = true;
410                        if (!self.options.allowMultiSelect) {
411                            $control.find('.picker-btn').trigger('click');
412                        }
413                    })
414                    .on('rockTree:expand rockTree:collapse rockTree:dataBound', function (evt, data) {
415                        self.updateScrollbar();
416                        // Get any node item so we can read to total count
417                        const rockTree = $control.find('.treeview').data('rockTree');
418                        const firstNode = rockTree.nodes[0];
419                        if (firstNode && firstNode.totalCount > 1000) {
420                            $control.find('.js-select-all').hide();
421                        }
422
423                        if (self.selectAll) {
424                            self.toggleSelectAll(rockTree);
425                        }
426                    })
427                    .on('rockTree:rendered', function (evt, data) {
428                        var rockTree = $control.find('.treeview').data('rockTree');
429                        self.createSearchControl();
430                        self.showActiveMenu();
431                        self.scrollToSelectedItem();
432
433                        if ($hfItemIds && $hfItemIds.val().length > 0) {
434                            var itemIds = $hfItemIds.val().split(',');
435
436                            rockTree.setSelected(itemIds);
437                        }
438
439                        self.togglePickerElements();
440
441                        if (self.getViewMode() === 'selecting') {
442                            self.setViewMode('clear');
443                            $control.find('.picker-btn').trigger('click', [$hfItemIds.val()]);
444                        }
445
446                        if (self.getViewMode() === 'selected') {
447                            if ($hfItemIds.val() === '0' && !$control.find('.picker-menu').is(':visible') ) {
448                                return;
449                            }
450
451                            $control.find('.picker-cancel').trigger('click');
452                        }
453                    })
454                    .on('rockTree:fetchCompleted', function (evt, data) {
455                        // intentionally empty
456                    })
457
458                $control.find('.picker-label').on('click', function (e) {
459                    e.preventDefault();
460                    $(this).toggleClass("active");
461                    $control.find('.picker-menu').first().toggle(0, function () {
462                        self.scrollToSelectedItem();
463                        $control.find('.js-search-panel input').trigger('focus');
464                    });
465                });
466
467                $control.find('.picker-cancel').on('click', function () {
468                    $(this).toggleClass("active");
469                    $control.find('.picker-menu').hide(0, function () {
470                        self.updateScrollbar();
471                    });
472                    $(this).closest('.picker-label').toggleClass("active");
473
474                    //cleanup
475                    self.setViewMode('clear');
476                    self.setActiveMenu(false);
477                    //doPostBack();
478                });
479
480                // Preview Selection link click
481                $control.find('.picker-preview').on('click', function () {
482                    $control.find('.js-search-panel').hide();
483                    $control.find('.picker-preview').hide();
484                    $control.find('.picker-treeview').show();
485                    $control.find('.js-select-all').hide();
486
487                    self.setViewMode('preview');
488                    self.togglePickerElements();
489
490                    var rockTree = $control.find('.treeview').data('rockTree');
491
492                    // Get all of the current rendered items so we can revert if user leaves search
493                    self.currentTreeView = $control.find('.treeview').html();
494
495                    var $viewport = $control.find('.viewport');
496
497                    if (rockTree.selectedNodes && rockTree.selectedNodes.length > 0) {
498
499                        var listHtml = '';
500                        rockTree.selectedNodes.forEach(function (node) {
501                            if (!node.path) {
502                                node.path = 'Top-Level';
503                            }
504
505                            listHtml +=
506                                '<div id="preview-item-' + node.id + '" class="d-flex align-items-center preview-item js-preview-item">' +
507                                '         <div class="flex-fill">' +
508                                '              <span class="text-color d-block">' + node.name + '</span>' +
509                                '              <span class="text-muted text-sm">' + node.path.replaceAll('^', '<i class="ti ti-chevron-right pl-1 pr-1" aria-hidden="true"></i>') + '</span>' +
510                                '         </div>' +
511                                '         <a id="lnk-remove-preview-' + node.id + '" title="Remove From Preview" class="btn btn-link text-color btn-xs btn-square ml-auto js-remove-preview" data-id="' + node.id + '"><i class="ti ti-x"></i></a>' +
512                                '</div>';
513                        });
514
515                        // Display preview list
516                        var listHtmlView = '<div>' +
517                            listHtml +
518                            '</div>';
519
520                        $control.find('.scroll-container').addClass('scroll-container-native').html(listHtmlView);
521                        // Wire up remove event and remove from dataset
522
523                        $control.find('.js-remove-preview').on('click', function (e) {
524                            var nodeId = $(this).attr('data-id');
525
526                            $control.find('#preview-item-' + nodeId).remove();
527
528                            var newSelected = rockTree.selectedNodes.filter(function (fNode) {
529                                return fNode.id !== nodeId;
530                            });
531
532                            rockTree.selectedNodes = newSelected;
533
534                            // After removing a selection we need to update the hf value
535                            var newIds = newSelected.map(function (n) { return n.id });
536
537                            if (newIds && newIds.length > 0) {
538                                $hfItemIds.val(newIds.join(','));
539                            }
540                            else {
541                                $hfItemIds.val('0');
542                            }
543
544                            if ($control.find('.js-preview-item').length === 0) {
545                                $control.find('.picker-treeview').trigger('click');
546                            }
547                        });
548                    }
549                });
550
551                // Tree View link click
552                $control.find('.picker-treeview').on('click', function () {
553                    $searchValueField.val('');
554                    self.setActiveMenu(true);
555                    self.setViewMode('clear');
556
557                    if ($hfItemIds && $hfItemIds.length > 0) {
558                        var restUrl = self.options.getParentIdsUrl
559                        var nodeIds = $hfItemIds.val().split(',');
560                        var restUrlParams = nodeIds.map(function (id) { return 'ids=' + id }).join('&');
561
562                        restUrl = restUrl + '?' + restUrlParams;
563
564                        // Get the ancestor ids so the tree will expand
565                        $.getJSON(restUrl, function (data, status) {
566                            if (data && status === 'success') {
567                                var selectedIds = [];
568                                var expandedIds = [];
569
570                                $.each(data, function (key, value) {
571                                    selectedIds.push(key);
572
573                                    value.forEach(function (kval) {
574                                        if (!expandedIds.find(function (expVal) {
575                                            return expVal === kval
576                                        })) {
577                                            expandedIds.push(kval);
578                                        }
579                                    });
580                                });
581
582                                if (expandedIds && expandedIds.length > 0) {
583                                    $hfExpandedIds.val(expandedIds.join(','));
584                                }
585
586                                if (selectedIds && selectedIds.length > 0) {
587                                    $hfItemIds.val(selectedIds.join(','));
588                                }
589
590                                doPostBack();
591                            }
592                        });
593                    }
594
595                });
596
597                // have the X appear on hover if something is selected
598                if ($hfItemIds.val() && $hfItemIds.val() !== '0') {
599                    $control.find('.picker-select-none').addClass('rollover-item');
600                    $control.find('.picker-select-none').show();
601                }
602
603                // [Select] button click
604                $control.find('.picker-btn').on('click', function (el) {
605
606                    if (self.getViewMode() === 'preview' || self.getViewMode() === 'search') {
607                        self.setViewMode('selecting');
608                        $('#tbSearch_' + self.options.controlId).val('');
609                        $control.find('.js-existing-search-value').val('');
610
611                        if ($hfItemIds && $hfItemIds.length > 0) {
612                            var restUrl = self.options.getParentIdsUrl
613                            var nodeIds = $hfItemIds.val().split(',');
614                            var restUrlParams = nodeIds.map(function (id) { return 'ids=' + id }).join('&');
615
616                            restUrl = restUrl + '?' + restUrlParams;
617
618                            // Get the ancestor ids so the tree will expand
619                            $.getJSON(restUrl, function (data, status) {
620                                if (data && status === 'success') {
621                                    var selectedIds = [];
622                                    var expandedIds = [];
623
624                                    $.each(data, function (key, value) {
625                                        selectedIds.push(key);
626
627                                        value.forEach(function (kval) {
628                                            if (!expandedIds.find(function (expVal) {
629                                                return expVal === kval
630                                            })) {
631                                                expandedIds.push(kval);
632                                            }
633                                        });
634                                    });
635
636                                    if (expandedIds && expandedIds.length > 0) {
637                                        $hfExpandedIds.val(expandedIds.join(','));
638                                    }
639
640                                    if (selectedIds && selectedIds.length > 0) {
641                                        $hfItemIds.val(selectedIds.join(','));
642                                    }
643
644                                    doPostBack();
645                                }
646                            });
647                        }
648
649                        return;
650                    }
651
652                    var rockTree = $control.find('.treeview').data('rockTree'),
653                        selectedNodes = rockTree.selectedNodes,
654                        selectedIds = [],
655                        selectedNames = [];
656
657                    $.each(selectedNodes, function (index, node) {
658                        var nodeName = $("<textarea/>").html(node.name).text();
659                        selectedNames.push(nodeName);
660                        if (!selectedIds.includes(node.id)) {
661                            selectedIds.push(node.id);
662                        }
663                    });
664
665                    // .trigger('change') is used to cause jQuery to fire any "onchange" event handlers for this hidden field.
666                    $hfItemIds.val(selectedIds.join(',')).trigger('change');
667                    $hfItemNames.val(selectedNames.join(','));
668
669                    // have the X appear on hover. something is selected
670                    $control.find('.picker-select-none').addClass('rollover-item');
671                    $control.find('.picker-select-none').show();
672
673                    $spanNames.text(selectedNames.join(', '));
674                    $spanNames.attr('title', $spanNames.text());
675
676                    $control.find('.picker-label').toggleClass("active");
677                    $control.find('.picker-menu').hide(0, function () {
678                        self.updateScrollbar();
679                    });
680
681                    self.setViewMode('selected');
682
683                    if (!(el && el.originalEvent && el.originalEvent.srcElement === this)) {
684                        // if this event was called by something other than the button itself, make sure the execute the href (which is probably javascript)
685                        var jsPostback = $(this).attr('href');
686                        if (jsPostback) {
687                            window.location = jsPostback;
688                        }
689                    }
690                });
691
692                $control.find('.picker-select-none').on("click", function (e) {
693                    e.preventDefault();
694                    e.stopImmediatePropagation();
695
696                    $hfItemIds.val('0').trigger('change'); // .trigger('change') is used to cause jQuery to fire any "onchange" event handlers for this hidden field.
697                    $hfItemNames.val('');
698
699                    var rockTree = $control.find('.treeview').data('rockTree');
700                    rockTree.clear();
701
702                    // don't have the X appear on hover. nothing is selected
703                    $control.find('.picker-select-none').removeClass('rollover-item').hide();
704
705                    $control.siblings('.js-hide-on-select-none').hide();
706
707                    $spanNames.text(self.options.defaultText);
708                    $spanNames.attr('title', $spanNames.text());
709                    doPostBack();
710                });
711
712                // clicking on the 'select all' btn
713                $control.on('click', '.js-select-all', function (e){
714                    const $tree = $control.find('.treeview');
715                    const rockTree = $tree.data('rockTree');
716                    self.selectAll = !self.selectAll;
717
718                    e.preventDefault();
719                    e.stopPropagation();
720
721                    let isChildrenLoaded = true;
722                    for (const node of rockTree.nodes) {
723                        if (node.hasChildren && !node.children) {
724                            isChildrenLoaded = false;
725                        }
726                    }
727
728                    if (!isChildrenLoaded) {
729                        rockTree.nodes = [];
730                        rockTree.render();
731                        let treeOptions = {
732                            customDataItems: self.options.customDataItems,
733                            enhanceForLongLists: self.options.enhanceForLongLists,
734                            multiselect: self.options.allowMultiSelect,
735                            categorySelection: self.options.allowCategorySelection,
736                            categoryPrefix: self.options.categoryPrefix,
737                            restUrl: self.options.restUrl,
738                            searchRestUrl: self.options.searchRestUrl,
739                            restParams: self.options.restParams + '&loadChildren=true',
740                            expandedIds: self.options.expandedIds,
741                            expandedCategoryIds: self.options.expandedCategoryIds,
742                            showSelectChildren: self.options.showSelectChildren,
743                            id: self.options.startingId
744                        };
745                        $tree.rockTree(treeOptions);
746                    } else {
747                        self.toggleSelectAll(rockTree);
748                    }
749                });
750            },
751            updateScrollbar: function (sPosition) {
752                var self = this;
753                // first, update this control's scrollbar, then the modal's
754                var $container = $('#' + this.options.controlId).find('.scroll-container');
755
756                if ($container.is(':visible')) {
757                    if (!sPosition) {
758                        sPosition = 'relative'
759                    }
760                    if (self.iScroll) {
761                        self.iScroll.refresh();
762                    }
763                }
764
765                // update the outer modal
766                Rock.dialogs.updateModalScrollBar(this.options.controlId);
767            },
768            scrollToSelectedItem: function () {
769                var $selectedItem = $('#' + this.options.controlId + ' [class^="picker-menu"]').find('.selected').first();
770                if ($selectedItem.length && (!this.alreadyScrolledToSelected)) {
771                    this.updateScrollbar();
772                    this.iScroll.scrollToElement('.selected', '0s');
773                    this.alreadyScrolledToSelected = true;
774                } else {
775                    // initialize/update the scrollbar
776                    this.updateScrollbar();
777                }
778            },
779            showActiveMenu: function () {
780                var self = this;
781                var $control = $('#' + this.options.controlId);
782
783                if (self.isMenuActive()) {
784                    $control.find('.picker-label').click();
785                }
786                self.setActiveMenu(false);
787            },
788            setActiveMenu: function (value) {
789                var $control = $('#' + this.options.controlId);
790                if (value === undefined || value === null) {
791                    value = true;
792                }
793                $control.find('.js-picker-showactive-value').val(value.toString());
794            },
795            isMenuActive: function () {
796                var $control = $('#' + this.options.controlId);
797                var showPickerActive = $control.find('.js-picker-showactive-value').val();
798                var isActive = showPickerActive && showPickerActive === 'true' ? true : false;
799                return isActive;
800            },
801            setViewMode: function (mode) {
802                var $control = $('#' + this.options.controlId);
803                var $hfViewMode = $control.find('.js-picker-view-mode');
804
805                var clear = mode.toLowerCase() === 'clear';
806
807                if (!clear && clear === false) {
808                    $hfViewMode.val(mode);
809                }
810
811                if (clear && clear === true) {
812                    $hfViewMode.val('');
813                }
814            },
815            getViewMode: function (mode) {
816                var $control = $('#' + this.options.controlId);
817                var $hfViewMode = $control.find('.js-picker-view-mode');
818
819                return $hfViewMode.val().toLowerCase();
820            },
821            togglePickerElements: function () {
822                var $control = $('#' + this.options.controlId);
823                var rockTree = $control.find('.treeview').data('rockTree');
824
825                var hasSelected = rockTree.selectedNodes && rockTree.selectedNodes.length > 0;
826
827                switch (this.getViewMode()) {
828                    case 'preview': {
829                        $control.find('.picker-treeview').show();
830                        $control.find('.picker-preview').hide();
831                        $control.find('.js-picker-show-inactive').hide();
832                    }
833                        break;
834                    case 'search': {
835                        $control.find('.picker-treeview').show();
836                        $control.find('.picker-preview').hide();
837                        $control.find('.js-select-all').hide();
838                    }
839                        break;
840                    default: {
841                        if (!hasSelected) {
842                            $control.find('.picker-preview').hide();
843                            $control.find('.picker-treeview').hide();
844                        }
845                        else {
846                            $control.find('.picker-preview').show();
847                        }
848                    }
849
850                }
851            },
852            findNodes: function (allNodes, selectNodeIds) {
853                if (selectNodeIds) {
854                    if ($.isArray(selectNodeIds)) {
855                        const filterArray = (nodes, ids) => {
856                            const filteredNodes = nodes.filter(node => {
857                                return ids.indexOf(node.id) >= 0;
858                            });
859                            return filteredNodes;
860                        };
861
862                        return filterArray(allNodes, selectNodeIds);
863                    }
864                    else {
865                        return allNodes.filter(node => {
866                            return selectNodeIds.indexOf(node.id) >= 0;
867                        });
868                    }
869                }
870            },
871            createSearchControl: function () {
872                var self = this;
873                var controlId = self.options.controlId;
874
875                var $control = $('#' + controlId);
876
877                var rockTree = $control.find('.treeview').data('rockTree');
878
879                if (self.options.enhanceForLongLists === true) {
880
881                    // A hidden value to store the current search criteria to be read on post-backs
882                    var $searchValueField = $control.find('.js-existing-search-value');
883
884                    var $searchControl =
885                        $('	<div id="pnlSearch_' + controlId + '" class="input-group input-group-sm js-search-panel mb-2" > ' +
886                            '		<input id="tbSearch_' + controlId + '" type="text" placeholder="Quick Find" class="form-control" autocapitalize="off" autocomplete="off" autocorrect="off" spellcheck="false" />' +
887                            '		<span class="input-group-btn">' +
888                            '			<a id="btnSearch_' + controlId + '" class="btn btn-default btn-sm"><i class="ti ti-search"></i></a>' +
889                            '		</span>' +
890                            '	</div>');
891
892                    // Get all of the current rendered items so we can revert if user leaves search
893                    var $pickerMenu = $control.find('.picker-menu');
894                    var $treeView = $control.find('.treeview');
895                    var $overview = $control.find('.overview');
896                    var $hfItemIds = $control.find('.js-item-id-value');
897
898                    // Added this check to prevent rendering call from duping the element
899                    if ($pickerMenu.find('.js-search-panel').length === 0) {
900                        // Add the search control after rendering
901                        $pickerMenu.prepend($searchControl);
902                    }
903
904                    var $searchInputControl = $('#tbSearch_' + controlId);
905
906                    $('#btnSearch_' + controlId).off('click').on('click', function () {
907
908                        var currentSelectedNodeIds = rockTree.selectedNodes.map(function (v) { return v.id; });
909
910                        $hfItemIds = $control.find('.js-item-id-value');
911
912                        var searchKeyword = $searchInputControl.val();
913
914                        if (searchKeyword && searchKeyword.length > 0) {
915
916                            self.setViewMode('search');
917
918                            var searchRestUrl = self.options.searchRestUrl;
919                            var restUrlParams = self.options.restParam
919s + '&searchTerm=' + searchKeyword;
920
921                            searchRestUrl += restUrlParams;
922
923                            $.getJSON(searchRestUrl, function (data, status) {
924
925                                if (data && status === 'success') {
926                                    $treeView.html('');
927                                }
928                                else {
929                                    $overview.html(treeView);
930                                    return;
931                                }
932
933                                // Create the search results node object
934                                var nodes = [];
935                                for (var i = 0; i < data.length; i++) {
936                                    var obj = data[i];
937                                    var node = {
938                                        id: obj.Id,
939                                        parentId: obj.ParentId,
940                                        glcode: obj.GlCode,
941                                        title: obj.Name + (obj.GlCode ? ' (' + obj.GlCode + ')' : ''),
942                                        name: obj.Name,
943                                        hasChildren: obj.HasChildren,
944                                        isActive: obj.IsActive,
945                                        path: obj.Path
946                                    };
947
948                                    nodes.push(node);
949                                }
950
951                                if (nodes) {
952
953                                    var listHtml = '';
954                                    nodes.forEach(function (node, idx) {
955
956                                        var disabledCheck = '';
957                                        var mutedText = '';
958                                        if (!node.isActive || node.isActive === false) {
959                                            disabledCheck = ' disabled';
960                                            mutedText = ' text-muted';
961                                        }
962
963                                        var inputHtml = '<input type="radio" data-id="' + node.id + '" class="checkbox js-opt-search"' + disabledCheck + '>';
964                                        var inputType = 'radio';
965                                        if (self.options.allowMultiSelect) {
966                                            inputHtml = '<input type="checkbox" data-id="' + node.id + '" class="checkbox js-chk-search"' + disabledCheck + '>';
967                                            inputType = 'checkbox';
968                                        }
969
970                                        if (node.path === '') {
971                                            node.path = "Top-Level";
972                                        }
973
974                                        listHtml +=
975
976                                            '<div id="divSearchItem-' + node.id + '" class="' + inputType + ' search-item js-search-item">' +
977                                            '      <label>' +
978                                            inputHtml +
979                                            '        <span class="label-text">' +
980                                            '              <span class="text-color d-block' + mutedText + '">' + node.title + '</span>' +
981                                            '              <span class="text-muted text-sm">' + node.path.replaceAll('^', '<i class="ti ti-chevron-right pl-1 pr-1" aria-hidden="true"></i>') + '</span>' +
982                                            '        </span>' +
983                                            '     </label>' +
984                                            '</div>';
985                                    });
986
987                                    // add the results to the panel
988                                    $treeView.html(listHtml);
989
990                                    self.updateScrollbar();
991
992                                    $control.find('.js-chk-search').off('change').on('change', function () {
993                                        var itemIds = [];
994                                        var $allChecked = $control.find('.js-chk-search:checked');
995                                        var checkedVals = $allChecked.map(function () {
996                                            return $(this).attr('data-id');
997                                        }).get();
998
999                                        if (checkedVals && checkedVals.length > 0) {
1000                                            $control.find('.picker-treeview').show();
1001                                            var checkedNodes = self.findNodes(nodes, checkedVals);
1002
1003                                            if (checkedNodes && checkedNodes.length) {
1004                                                checkedNodes.forEac
1004h(function (n) {
1005                                                    if (n.length === 0) { return; }
1006                                                    if (!itemIds.find(function (i) { return n.id === i; })) {
1007                                                        itemIds.push(n.id);
1008                                                    }
1009
1010                                                });
1011                                            }
1012                                        }
1013
1014                                        // We need to reselect any selection that were set in the tree but not part of the search results
1015                                        if (currentSelectedNodeIds && currentSelectedNodeIds.length > 0) {
1016                                            nodes.forEach(function (node) {
1017                                                currentSelectedNodeIds = currentSelectedNodeIds.filter(function (current) { return current !== node.id });
1018                                            });
1019
1020                                            if (currentSelectedNodeIds && currentSelectedNodeIds.length > 0) {
1021                                                itemIds.push(currentSelectedNodeIds);
1022                                            }
1023                                        }
1024
1025                                        if (itemIds && itemIds.length) {
1026                                            // set the selected items on the server control to handle on postback
1027                                            $hfItemIds.val(itemIds.join(','));
1028                                        }
1029                                    });
1030
1031                                    // Handle multi item check selection
1032                                    $control.find('.js-search-item').off('click').on('click', function (e) {
1033
1034                                        var $chkBox = $(this).find('.js-chk-search').first();
1035                                        if ($chkBox.length > 0) {
1036                                            if (!$chkBox.prop('checked')) {
1037                                                $chkBox.prop('checked', true);
1038                                                $control.find('.js-chk-search').trigger('change');
1039                                            }
1040                                            else {
1041                                                $chkBox.prop('checked', false);
1042                                            }
1043                                        }
1044                                    });
1045
1046
1047                                    // Handle single item radio selection
1048                                    $control.find('.js-opt-search').off('change').on('change', function () {
1049                                        var thisNodeId = $(this).attr('data-id');
1050                                        var itemIds = [];
1051                                        //prevent multi select
1052                                        $control.find('.js-opt-search:not([data-id=' + thisNodeId + '])').prop('checked', false);
1053
1054                                        var $allChecked = $control.find('.js-opt-search:checked');
1055                                        var checkedVals = $allChecked.map(function () {
1056                                            return $(this).attr('data-id');
1057                                        }).get();
1058
1059                                        if (checkedVals && checkedVals.length > 0) {
1060                                            var checkedNodes = self.findNodes(nodes, checkedVals);
1061
1062                                            if (checkedNodes && checkedNodes.length) {
1063                                                checkedNodes.forEach(function (n) {
1064                                                    if (n.length === 0) { return; }
1065                                                    if (!itemIds.find(function (i) { return n.id === i; })) {
1066                                                        itemIds.push(n.id);
1067                                                    }
1068                                                });
1069                                            }
1070                                        }
1071
1072                                        if (itemIds && itemIds.length) {
1073                                            // set the selected items on the server control to handle on postback
1074                                            $hfItemIds.val(itemIds.join(','));
1075                                        }
1076                                    });
1077                                }
1078
1079                                //select current selected nodes in search
1080                                if (rockTree.selectedNodes && rockTree.selectedNodes.length) {
1081                                    rockTree.selectedNodes.forEac
1081h(function (selectedNode) {
1082                                        if (self.options.allowMultiSelect && self.options.allowMultiSelect === true) {
1083                                            $control.find('[data-id=' + selectedNode.id + '].js-chk-search').prop('checked', true);
1084                                            var checkedNode = selectedNode;
1085                                            checkedNode.id = selectedNode.id;
1086                                            $control.find('[data-id=' + selectedNode.id + '].js-chk-search').trigger('change');
1087                                        }
1088                                        else {
1089                                            $control.find('[data-id=' + selectedNode.id + '].js-opt-search').prop('checked', true);
1090                                            var enabledNode = selectedNode;
1091                                            enabledNode.id = selectedNode.id;
1092                                            $control.find('[data-id=' + selectedNode.id + '].js-opt-search').trigger('change');
1093                                        }
1094                                    });
1095                                }
1096                            });
1097                        }
1098
1099                    });
1100
1101                    // If we have an existing search value on postback
1102                    if ($searchValueField.length > 0 && $searchValueField.val().length > 0) {
1103                        $searchInputControl.val($searchValueField.val());
1104                        $('#btnSearch_' + controlId).click();
1105                    }
1106
1107                    // Handle the input searching
1108                    $searchInputControl.keyup(function (keyEvent) {
1109                        keyEvent.preventDefault();
1110
1111                        var searchKeyword = $searchInputControl.val();
1112
1113                        if (!searchKeyword || searchKeyword.length === 0) {
1114                            $control.find('.picker-treeview').trigger('click');
1115                        }
1116
1117                        self.togglePickerElements();
1118                    }).keydown(function (keyEvent) {
1119
1120                        var searchKeyword = $searchInputControl.val();
1121                        $searchValueField.val(searchKeyword);
1122
1123                        if (keyEvent.which === 13) {
1124                            keyEvent.preventDefault();
1125                            $('#btnSearch_' + controlId).click();
1126                        }
1127                    });
1128                }
1129            },
1130            toggleSelectAll: function (rockTree) {
1131
1132                const $itemNameNodes = rockTree.$el.find('.rocktree-name');
1133
1134                const setNodeOpenState = function (node, isOpen) {
1135                    if (node.hasChildren && node.children) {
1136                        node.isOpen = isOpen;
1137                        node.children.forEach(childNode => setNodeOpenState(childNode, isOpen));
1138                    }
1139                }
1140
1141                if (this.selectAll) {
1142                    // mark them all as unselected (just in case some are selected already), then click them to select them
1143                    $itemNameNodes.removeClass('selected');
1144                    $itemNameNodes.trigger('click');
1145                    rockTree.nodes.forEach(node => setNodeOpenState(node, true));
1146                } else {
1147                    // if all were already selected, toggle them to unselected
1148                    rockTree.setSelected([]);
1149                    const $control = $('#' + this.options.controlId);
1150                    const $hfItemIds = $control.find('.js-item-id-value');
1151                    $hfItemIds.val('0');
1152                    $itemNameNodes.removeClass('selected');
1153                    rockTree.nodes.forEach(node => setNodeOpenState(node, false));
1154                }
1155
1156                rockTree.render();
1157            },
1158            selectAll: false
1159        }
1160
1161        // jquery function to ensure HTML is state remains the same each time it is executed
1162        $.fn.outerHTML = function (s) {
1163            return (s)
1164                ? this.before(s).remove()
1165                : $("<p>").append(this.eq(0).clone()).html();
1166        }
1167
1168        exports = {
1169            defaults: {
1170                id: 0,
1171                controlId: null,
1172                restUrl: null,
1173                searchRestUrl: null,
1174                restParams: null,
1175                allowCategorySelection: false,
1176                categoryPrefix: '',
1177                allowMultiSelect: false,
1178                defaultText: '',
1179                selectedIds: null,
1180                expandedIds: null,
1181                expandedCategoryIds: null,
1182                showSelectChildren: false,
1183                enhanceForLongLists: false,
1184                customDataItems: []
1185            },
1186            controls: {},
1187            initialize: function (options) {
1188                var settings,
1189                    accountPicker;
1190
1191                if (!options.controlId) {
1192                    throw 'controlId must be set';
1193                }
1194
1195                if (!options.restUrl) {
1196                    throw 'restUrl must be set';
1197                }
1198
1199                if (options.enhanceForLongLists === true && !options.searchRestUrl) {
1200                    throw 'searchRestUrl must be set';
1201                }
1202
1203                settings = $.extend({}, exports.defaults, options);
1204
1205                if (!settings.defaultText) {
1206                    settings.defaultText = exports.defaults.defaultText;
1207                }
1208
1209                accountPicker = new AccountPicker(settings);
1210                exports.controls[settings.controlId] = accountPicker;
1211                accountPicker.initialize();
1212            }
1213        };
1214
1215        return exports;
1216    }());
1217}(jQuery));
1218;
1219(function ($) {
1220    'use strict';
1221    window.Rock = window.Rock || {};
1222    Rock.controls = Rock.controls || {};
1223
1224    Rock.controls.addressControl = (function () {
1225        var exports = {
1226            initialize: function (options) {
1227                if (!options.id) {
1228                    throw 'id is required';
1229                }
1230            },
1231            clientValidate: function (validator, args) {
1232                var $addressControl = $(validator).closest('.js-addressControl');
1233                var $addressFieldControls = $addressControl.find('.js-address-field.required');
1234                var addressControlLabelText = $addressControl.attr('data-itemlabel');
1235                var isValid = true;
1236                var invalidFieldList = [];
1237
1238                $addressFieldControls.each(function (index, fieldControl) {
1239                    var $fieldControl = $(fieldControl);
1240                    if (typeof fieldControl == 'undefined' || $fieldControl.val().length > 0) {
1241                        $fieldControl.parent('div.form-group').removeClass('has-error');
1242                    }
1243                    else {
1244                        $fieldControl.parent('div.form-group').addClass('has-error');
1245                        invalidFieldList.push($fieldControl.attr('field-name'));
1246                        isValid = false;
1247                    }
1248                });
1249
1250                if (!isValid) {
1251                    validator.errormessage = addressControlLabelText + " field is required: " + invalidFieldList.join(", ");
1252                }
1253
1254                args.IsValid = isValid;
1255            }
1256        };
1257
1258        return exports;
1259    }());
1260}(jQuery));
1261;
1262(function ($) {
1263    'use strict';
1264    window.Rock = window.Rock || {};
1265    Rock.controls = Rock.controls || {};
1266
1267    Rock.controls.assetManager = (function () {
1268        var exports;
1269
1270        exports = {
1271            initialize: function (options) {
1272                var self = this;
1273
1274                var $assetBrowser = $('#' + options.controlId);
1275                var $folderTreeView = $assetBrowser.find('.js-folder-treeview .treeview');
1276                var $selectFolder = $assetBrowser.find('.js-selectfolder');
1277                var $expandedFolders = $assetBrowser.find('.js-expandedFolders');
1278                var $assetStorageId = $assetBrowser.find('.js-assetstorage-id');
1279                var $treePort = $assetBrowser.find('.js-treeviewport');
1280                var $treeTrack = $assetBrowser.find('.js-treetrack');
1281                var $isRoot = $assetBrowser.find('.js-isroot');
1282
1283                var $createFolder = $assetBrowser.find('.js-createfolder');
1284                var $createFolderDiv = $assetBrowser.find('.js-createfolder-div');
1285                var $createFolderInput = $assetBrowser.find('.js-createfolder-input');
1286                var $createFolderCancel = $assetBrowser.find('.js-createfolder-cancel');
1287                var $createFolderAccept = $assetBrowser.find('.js-createfolder-accept');
1288                var $deleteFolder = $assetBrowser.find('.js-deletefolder');
1289                var $createFolderNotification = $assetBrowser.find('.js-createfolder-notification');
1290
1291
1292                // 2020-09-07 MDP
1293                // Asset manager can be used in 3 ways.
1294                // 1) An ItemFromBlockPicker control (An Asset attribute).
1295                // 2) Within the HTML Editor as a summernote plugin.
1296                // 3) In Home > CMS Configuration > Asset Manager.
1297                // The OK/Save button is the parent modal (but different types of modals) in case 1 and 2, and without an OK/Save button in case 3.
1298                // So to find all the js-singleselect items in cases 1 and 2, we'll have to look in the modal so that the OK/Save button is found
1299                var $assetManagerModal = $assetBrowser.closest('.js-AssetManager-modal');
1300                var $singleSelectControls;
1301                if ($assetManagerModal.length) {
1302                    // this would be cases #1 and #2
1303                    $singleSelectControls = $assetManagerModal.find('.js-singleselect');
1304                } else {
1305                    // this would be case #3
1306                    $singleSelectControls = $assetBrowser.find('.js-singleselect');
1307                }
1308
1309                var $minSelectControls = $assetBrowser.find('.js-minselect');
1310                var $fileCheckboxes = $assetBrowser.find('.js-checkbox');
1311
1312                var $renameFile = $assetBrowser.find('.js-renamefile');
1313                var $renameFileDiv = $assetBrowser.find('.js-renamefile-div');
1314                var $renameFileInput = $assetBrowser.find('.js-renamefile-input');
1315                var $renameFileCancel = $assetBrowser.find('.js-renamefile-cancel');
1316                var $renameFileAccept = $assetBrowser.find('.js-renamefile-accept');
1317                var $renamefilenotification = $assetBrowser.find('.js-renamefile-notification');
1318
1319                if ($folderTreeView.length === 0) {
1320                    return;
1321                }
1322
1323                // Some buttons need an asset selected in order to work
1324                if ($assetStorageId.val() === "-1") {
1325                    $('.js-assetselect').addClass('aspNetDisabled');
1326                }
1327
1328                $fileCheckboxes.removeAttr('checked');
1329
1330                // Can only add if either an asset or folder is selected
1331                if ($selectFolder.val() === "" && $assetStorageId.val() === "-1") {
1332                    $createFolder.addClass('aspNetDisabled');
1333                }
1334                else {
1335                    $createFolder.removeClass('aspNetDisabled');
1336                }
1337
1338                // Can delete folder only if a folder is selected
1339                if ($selectFolder.val() === "" || $isRoot.val() === "True") {
1340                    $deleteFolder.addClass('aspNetDisabled');
1341                }
1342                else {
1343                    $deleteFolder.removeClass('aspNetDisabled');
1344                }
1345
1346                var folderTreeData = $folderTreeView.data('rockTree');
1347
1348                if (!folderTreeData) {
1349                    var selectedFolders = [encodeURIComponent($assetStorageId.val() + ',' + $selectFolder.val())];
1350                    var expandedFolders = $expandedFolders.val().split('||');
1351                    expandedFolders.forEach(function (part, i, array) {
1352                        array[i] = encodeURIComponent(array[i]);
1353                    });
1354
1355                    $folderTreeView.rockTree({
1356                        restUrl: options.restUrl,
1357                        selectedIds: selectedFolders,
1358                        expandedIds: expandedFolders
1359                    });
1360
1361                    var treePortIScroll = new IScroll($treePort[0], {
1362                        mouseWheel: false,
1363                        scrollX: true,
1364                        scrollY: false,
1365                        indicators: {
1366                            el: '#' + $treeTrack.attr('id'),
1367                            interactive: true,
1368                            resize: false,
1369                            listenY: false,
1370                            listenX: true
1371                        },
1372                        click: false,
1373                        preventDefaultException: { tagName: /.*/ }
1374                    });
1375
1376                    // resize scrollbar when the window resizes
1377                    $(document).ready(function () {
1378                        $(window).on('resize', function () {
1379                            resizeScrollAreaHeight();
1380                        });
1381                    });
1382
1383                    $folderTreeView.on('rockTree:dataBound rockTree:rendered', function (evt) {
1384                        resizeScrollAreaHeight();
1385                    });
1386
1387                    $folderTreeView.on('rockTree:expand rockTree:collapse', function (evt, data) {
1388                        resizeScrollAreaHeight();
1389
1390                        // get the data-id values of rock-tree items that are showing visible children (in other words, Expanded Nodes)
1391                        var expandedDataIds = $(evt.currentTarget).find('.rocktree-children').filter(":visible").closest('.rocktree-item').map(function () {
1392                            var dataId = $(this).attr('data-id');
1393                            if (dataId !== data) {
1394                                return decodeURIComponent(dataId);
1395                            }
1396                        }).get().join('||');
1397
1398                        $expandedFolders.val(expandedDataIds);
1399                    });
1400                }
1401
1402                function resizeScrollAreaHeight() {
1403                    var overviewHeight = $folderTreeView.closest('.overview').height();
1404                    $treePort.height(overviewHeight);
1405
1406                    if (treePortIScroll) {
1407                        treePortIScroll.refresh();
1408                    }
1409                }
1410
1411                function isValidName(name) {
1412                    var regex = new RegExp("^[^*/><?\\\\|:,~]+$");
1413                    return regex.test(name);
1414                }
1415
1416                $folderTreeView.off('rockTree:selected').on('rockTree:selected', function (e, data) {
1417                    var assetFolderIdParts = unescape(data).split(",");
1418                    var storageId = assetFolderIdParts[0] || "";
1419                    var folder = assetFolderIdParts[1] || "";
1420                    var isRoot = assetFolderIdParts[2] || false;
1421                    var postbackArg;
1422                    var expandedFolders = encodeURIComponent($expandedFolders.val());
1423
1424                    // Can only add if either an asset or folder is selected
1425                    if ($selectFolder.val() === "" && $assetStorageId.val() === "-1") {
1426                        $createFolder.addClass('aspNetDisabled');
1427                    }
1428                    else {
1429                        $createFolder.removeClass('aspNetDisabled');
1430                    }
1431
1432                    // Can delete folder only if a folder is selected
1433                    if ($selectFolder.val() === "" || $isRoot.val() === "True") {
1434                        $deleteFolder.addClass('aspNetDisabled');
1435                    }
1436                    else {
1437                        $deleteFolder.removeClass('aspNetDisabled');
1438                    }
1439
1440                    if ($selectFolder.val() !== folder || $assetStorageId.val() !== storageId) {
1441                        $selectFolder.val(folder);
1442                        $assetStorageId.val(storageId);
1443                        $isRoot.val(isRoot);
1444                        postbackArg = 'storage-id:' + storageId + '?folder-selected:' + folder.replace(/\\/g, "/").replace("'", "\\'") + '?expanded-folders:' + expandedFolders.replace("'", "\\'") + '?isRoot:' + isRoot;
1445
1446                        var jsPostback = "javascript:__doPostBack('" + options.filesUpdatePanelId + "','" + postbackArg + "');";
1447                        window.location = jsPostback;
1448                    }
1449                });
1450
1451                $fileCheckboxes.off('click').on('click', function () {
1452                    var numberChecked = $fileCheckboxes.filter(':checked').length;
1453
1454                    if (numberChecked === 0) {
1455                        $singleSelectControls.addClass('aspNetDisabled');
1456                        $('.js-minselect').addClass('aspNetDisabled');
1457                    }
1458                    else if (numberChecked === 1) {
1459                        $singleSelectControls.removeClass('aspNetDisabled');
1460                        $minSelectControls.removeClass('aspNetDisabled');
1461                    }
1462                    else {
1463                        $singleSelectControls.addClass('aspNetDisabled');
1464                        $minSelectControls.removeClass('aspNetDisabled');
1465                    }
1466                });
1467
1468                $createFolder.off('click').on('click', function () {
1469                    $createFolderDiv.fadeToggle();
1470                    $createFolderInput.val('');
1471                });
1472
1473                $createFolderCancel.off('click').on('click', function () {
1474                    $createFolderDiv.fadeOut();
1475                    $createFolderInput.val('');
1476                    $createFolderNotification.hide().text('');
1477                });
1478
1479                $createFolderAccept.off('click').on('click', function (e) {
1480                    var name = $createFolderInput.val();
1481
1482                    if (name === "") {
1483                        $createFolderNotification.show().text('Folder name is required.');
1484                        e.preventDefault();
1485                    }
1486
1487                    if (!isValidName(name)) {
1488                        $createFolderNotification.show().text('Invalid characters in path');
1489                        e.preventDefault();
1490                    }
1491                });
1492
1493                $renameFile.off('click').on('click', function () {
1494                    $renameFileDiv.fadeToggle();
1495                    $renameFileInput.val('');
1496                });
1497
1498                $renameFileCancel.off('click').on('click', function () {
1499                    $renameFileDiv.fadeOut();
1500                    $renameFileInput.val('');
1501                    $renamefilenotification.hide().text('');
1502                });
1503
1504                $renameFileAccept.off('click').on('click', function (e) {
1505                    var name = $renameFileInput.val();
1506
1507                    if (name === "") {
1508                        $renamefilenotification.show().text('File name is required.');
1509                        e.preventDefault();
1510                    }
1511
1512                    if (!isValidName(name)) {
1513                        $renamefilenotification.show().text('Invalid characters in file name.');
1514                        e.preventDefault();
1515                    }
1516                });
1517
1518                $deleteFolder.off('click').on('click', function (e) {
1519                    if ($(this).attr('disabled') === 'disabled') {
1520                        return false;
1521                    }
1522
1523                    Rock.dialogs.confirmDelete(e, 'folder and all its contents');
1524                });
1525            }
1526        };
1527
1528        return exports;
1529
1530    }());
1531}(jQuery));
1532;
1533(function ($) {
1534    'use strict';
1535    window.Rock = window.Rock || {};
1536    Rock.controls = Rock.controls || {};
1537
1538    Rock.controls.autoCompleteDropDown = (function () {
1539        var exports,
1540            AutoCompleteDropDown = function (options) {
1541                this.controlId = options.controlId;
1542                this.valueControlId = options.valueControlId;
1543                this.$el = $('#' + this.controlId);
1544                this.name = options.name;
1545                this.url = options.url;
1546                this.limit = options.limit;
1547                this.idProperty = options.idProperty;
1548                this.valuekey = options.valuekey;
1549                this.template = options.template;
1550                this.header = options.header;
1551                this.footer = options.footer;
1552            };
1553
1554        AutoCompleteDropDown.prototype = {
1555            constructor: AutoCompleteDropDown,
1556            initialize: function () {
1557                var self = this,
1558                    Liquid = require('liquid'),
1559                    liquidEngine = {
1560                        compile: function (template) {
1561                            var parsedTemplate = Liquid.Template.parse(template);
1562                            return {
1563                                render: function (context) {
1564                                    return parsedTemplate.render(context);
1565                                }
1566                            };
1567                        }
1568                    };
1569
1570                this.$el.typeahead({
1571                    name: this.name,
1572                    limit: this.limit,
1573                    valueKey: this.valuekey,
1574                    template: this.template,
1575                    header: this.header,
1576                    footer: this.footer,
1577                    engine: liquidEngine,
1578                    remote: {
1579                        url: Rock.settings.get('baseUrl') + this.url,
1580                        filter: function (response) {
1581                            response.forEach(function (item) {
1582                                item['tokens'] = item[self.valuekey].replace(/,/g, '').split(" ");
1583                            });
1584                            return response;
1585                        }
1586                    }
1587                });
1588
1589                this.initializeEventHandlers();
1590            },
1591
1592            initializeEventHandlers: function () {
1593                var self = this,
1594                    idProperty = this.idProperty,
1595                    setValue = function (datum) {
1596                        $('#' + self.valueControlId).val(datum[idProperty]);
1597                    };
1598
1599                // Listen for typeahead's custom events and trigger search when hit
1600                this.$el.on('typeahead:selected typeahead:autocompleted', function (e, obj, name) {
1601                    setValue(obj);
1602                    if (typeof self.onSelected === "function") {
1603                        self.onSelected(obj);
1604                    }
1605                });
1606
1607            }
1608        };
1609
1610        exports = {
1611            defaults: {
1612                controlId: null,
1613                name: 'autoComplete',
1614                valueControlId: null,
1615                limit: 5,
1616                idProperty: 'Id',
1617                valuekey: 'value',
1618                template: '<p>{{value}}</p>',
1619                header: '',
1620                footer: ''
1621            },
1622            initialize: function (options) {
1623                var autoCompleteDropDown,
1624                    settings = $.extend({}, exports.defaults, options);
1625
1626                if (!settings.controlId) throw 'controlId is required';
1627                if (!settings.valueControlId) throw 'valueControlId is required';
1628                if (!settings.url) throw 'url is required';
1629
1630                autoCompleteDropDown = new AutoCompleteDropDown(settings);
1631
1632                // Delay initialization until after the DOM is ready
1633                $(function () {
1634                    autoCompleteDropDown.initialize();
1635                });
1636
1637                return autoCompleteDropDown;
1638            }
1639        };
1640
1641        return exports;
1642    }());
1643}(jQuery));
1644;
1645/*!
1646 * Bootstrap Colorpicker v2.5.3
1647 * https://itsjavi.com/bootstrap-colorpicker/
1648 */
1649!function(a,b){"function"==typeof define&&define.amd?define(["jquery"],function(a){return b(a)}):"object"==typeof exports?module.exports=b(require("jquery")):jQuery&&!jQuery.fn.colorpicker&&b(jQuery)}(this,function(a){"use strict";var b=function(c,d,e,f,g){this.fallbackValue=e?"string"==typeof e?this.parse(e):e:null,this.fallbackFormat=f?f:"rgba",this.hexNumberSignPrefix=g===!0,this.value=this.fallbackValue,this.origFormat=null,this.predefinedColors=d?d:{},this.colors=a.extend({},b.webColors,this.predefinedColors),c&&("undefined"!=typeof c.h?this.value=c:this.setColor(String(c))),this.value||(this.value={h:0,s:0,b:0,a:1})};b.webColors={aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"00ffff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000000",blanchedalmond:"ffebcd",blue:"0000ff",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",cyan:"00ffff",darkblue:"00008b",darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgreen:"006400",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dodgerblue:"1e90ff",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"ff00ff",gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",green:"008000",greenyellow:"adff2f",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgrey:"d3d3d3",lightgreen:"90ee9
16490",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa",lightslategray:"778899",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"00ff00",limegreen:"32cd32",linen:"faf0e6",magenta:"ff00ff",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370d8",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080",oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"d87093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",red:"ff0000",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd",slategray:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",wheat:"f5deb3",white:"ffffff",whitesmoke:"f5f5f5",yellow:"ffff00",yellowgreen:"9acd32",transparent:"transparent"},b.prototype={constructor:b,colors:{},predefinedColors:{},getValue:function(){return this.value},setValue:function(a){this.value=a},_sanitizeNumber:function(a){return"number"==typeof a?a:isNaN(a)||null===a||""===a||void 0===a?1:""===a?0:"undefined"!=typeof a.toLowerCase?(a.match(/^\./)&&(a="0"+a),Math.ceil(100*parseFloat(a))/100):1},isTransparent:function(a){return!(!a||!("string"==typeof a||a instanceof String))&&(a=a.toLowerCase().trim(),"transparent"===a||a.match(/#?00000000/)||a.match(/(rgba|hsla)\(0,0,0,0?\.?0\)/))},rgbaIsTransparent:function(a){return 0===a.r&&0===a.g&&0===a.b&&0===a.a},setColor:function(a){if(a=a.toLowerCase().trim()){if(this.isTransparent(a))return this.value={h:0,s:0,b:0,a:0},!0;var b=this.parse(a);b?(this.value=this.value={h:b.h,s:b.s,b:b.b,a:b.a},this.origFormat||(this.origFormat=b.format)):this.fallbackValue&&(this.value=this.fallbackValue)}return!1},setHue:function(a){this.value.h=1-a},setSaturation:function(a){this.value.s=a},setBrightness:function(a){this.value.b=1-a},setAlpha:function(a){this.value.a=Math.round(parseInt(100*(1-a),10)/100*100)/100},toRGB:function(a,b,c,d){0===arguments.length&&(a=this.value.h,b=this.value.s,c=this.value.b,d=this.value.a),a*=360;var e,f,g,h,i;return a=a%360/60,i=c*b,h=i*(1-Math.abs(a%2-1)),e=f=g=c-i,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a],{r:Math.round(255*e),g:Math.round(255*f),b:Math.round(255*g),a:d}},toHex:function(a,b,c,d,e){arguments.length<=1&&(b=this.value.h,c=this.value.s,d=this.value.b,e=this.value.a);var f="#",g=this.toRGB(b,c,d,e);if(this.rgbaIsTransparent(g))return"transparent";a||(f=this.hexNumberSignPrefix?"#":"");var h=f+((1<<24)+(parseInt(g.r)<<16)+(parseInt(g.g)<<8)+parseInt(g.b)).toString(16).slice(1);return h},toHSL:function(a,b,c,d){0===arguments.length&&(a=this.value.h,b=this.value.s,c=this.value.b,d=this.value.a);var e=a,f=(2-b)*c,g=b*c;return g/=f>0&&f<=1?f:2-f,f/=2,g>1&&(g=1),{h:isNaN(e)?0:e,s:isNaN(g)?0:g,l:isNaN(f)?0:f,a:isNaN(d)?0:d}},toAlias:function(a,b,c,d){var e,f=0===arguments.length?this.toHex(!0):this.toHex(!0,a,b,c,d),g="alias"===this.origFormat?f:this.toString(!1,this.origFormat);for(var h in this.colors)if(e=this.colors[h].toLowerCase().trim(),e===f||e===g)return h;return!1},RGBtoHSB:function(a,b,c,d){a/=255,b/=255,c/=255;var e,f,g,h;return g=Math.max(a,b,c),h=g-Math.min(a,b,c),e=0===h?null:g===a?(b-c)/h:g===b?(c-a)/h+2:(a-b)/h+4,e=(e+360)%6*60/360,f=0===h?0:h/g,{h:this._sanitizeNumber(e),s:f,b:g,a:this._sanitizeNumber(d)}},HueToRGB:function(a,b,c){return c<0?c+=1:c>1&&(c-=1),6*c<1?a+(b-a)*c*6:2*c<1?b:3*c<2?a+(b-a)*(2/3-c)*6:a},HSLtoRGB:function(a,b,c,d){b<0&&(b=0);var e;e=c<=.5?c*(1+b):c+b-c*b;var f=2*c-e,g=a+1/3,h=a,i=a-1/3,j=Math.round(255*this.HueToRGB(f,e,g)),k=Math.round(255*this.HueToRGB(f,e,h)),l=Math.round(255*this.HueToRGB(f,e,i));return[j,k,l,this._sanitizeNumber(d)]},parse:function(b){if("string"!=typeof b)return this.fallbackValue;if(0===arguments.length)return!1;var c,d,e=this,f=!1,g="undefined"!=typeof this.colors[b];return g&&(b=this.colors[b].toLowerCase().trim()),a.each(this.stringParsers,function(a,h){var i=h.re.exec(b);return c=i&&h.parse.apply(e,[i]),!c||(f={},d=g?"alias":h.format?h.format:e.getValidFallbackFormat(),f=d.match(/hsla?/)?e.RGBtoHSB.apply(e,e.HSLtoRGB.apply(e,c)):e.RGBtoHSB.apply(e,c),f instanceof Object&&(f.format=d),!1)}),f},getValidFallbackFormat:function(){var a=["rgba","rgb","hex","hsla","hsl"];return this.origFormat&&a.indexOf(this.origFormat)!==-1?this.origFormat:this.fallbackFormat&&a.indexOf(this.fallbackFormat)!==-1?this.fallbackFormat:"rgba"},toString:function(a,c,d){c=c||this.origFormat||this.fallbackFormat,d=d||
vendor: 6,075 bytes, line 1649
1649!1;var e=!1;switch(c){case"rgb":return e=this.toRGB(),this.rgbaIsTransparent(e)?"transparent":"rgb("+e.r+","+e.g+","+e.b+")";case"rgba":return e=this.toRGB(),"rgba("+e.r+","+e.g+","+e.b+","+e.a+")";case"hsl":return e=this.toHSL(),"hsl("+Math.round(360*e.h)+","+Math.round(100*e.s)+"%,"+Math.round(100*e.l)+"%)";case"hsla":return e=this.toHSL(),"hsla("+Math.round(360*e.h)+","+Math.round(100*e.s)+"%,"+Math.round(100*e.l)+"%,"+e.a+")";case"hex":return this.toHex(a);case"alias":return e=this.toAlias(),e===!1?this.toString(a,this.getValidFallbackFormat()):d&&!(e in b.webColors)&&e in this.predefinedColors?this.predefinedColors[e]:e;default:return e}},stringParsers:[{re:/rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*?\)/,format:"rgb",parse:function(a){return[a[1],a[2],a[3],1]}},{re:/rgb\(\s*(\d*(?:\.\d+)?)\%\s*,\s*(\d*(?:\.\d+)?)\%\s*,\s*(\d*(?:\.\d+)?)\%\s*?\)/,format:"rgb",parse:function(a){return[2.55*a[1],2.55*a[2],2.55*a[3],1]}},{re:/rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d*(?:\.\d+)?)\s*)?\)/,format:"rgba",parse:function(a){return[a[1],a[2],a[3],a[4]]}},{re:/rgba\(\s*(\d*(?:\.\d+)?)\%\s*,\s*(\d*(?:\.\d+)?)\%\s*,\s*(\d*(?:\.\d+)?)\%\s*(?:,\s*(\d*(?:\.\d+)?)\s*)?\)/,format:"rgba",parse:function(a){return[2.55*a[1],2.55*a[2],2.55*a[3],a[4]]}},{re:/hsl\(\s*(\d*(?:\.\d+)?)\s*,\s*(\d*(?:\.\d+)?)\%\s*,\s*(\d*(?:\.\d+)?)\%\s*?\)/,format:"hsl",parse:function(a){return[a[1]/360,a[2]/100,a[3]/100,a[4]]}},{re:/hsla\(\s*(\d*(?:\.\d+)?)\s*,\s*(\d*(?:\.\d+)?)\%\s*,\s*(\d*(?:\.\d+)?)\%\s*(?:,\s*(\d*(?:\.\d+)?)\s*)?\)/,format:"hsla",parse:function(a){return[a[1]/360,a[2]/100,a[3]/100,a[4]]}},{re:/#?([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/,format:"hex",parse:function(a){return[parseInt(a[1],16),parseInt(a[2],16),parseInt(a[3],16),1]}},{re:/#?([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/,format:"hex",parse:function(a){return[parseInt(a[1]+a[1],16),parseInt(a[2]+a[2],16),parseInt(a[3]+a[3],16),1]}}],colorNameToHex:function(a){return"undefined"!=typeof this.colors[a.toLowerCase()]&&this.colors[a.toLowerCase()]}};var c={horizontal:!1,inline:!1,color:!1,format:!1,input:"input",container:!1,component:".add-on, .input-group-addon",fallbackColor:!1,fallbackFormat:"hex",hexNumberSignPrefix:!0,sliders:{saturation:{maxLeft:100,maxTop:100,callLeft:"setSaturation",callTop:"setBrightness"},hue:{maxLeft:0,maxTop:100,callLeft:!1,callTop:"setHue"},alpha:{maxLeft:0,maxTop:100,callLeft:!1,callTop:"setAlpha"}},slidersHorz:{saturation:{maxLeft:100,maxTop:100,callLeft:"setSaturation",callTop:"setBrightness"},hue:{maxLeft:100,maxTop:0,callLeft:"setHue",callTop:!1},alpha:{maxLeft:100,maxTop:0,callLeft:"setAlpha",callTop:!1}},template:'<div class="colorpicker dropdown-menu"><div class="colorpicker-saturation"><i><b></b></i></div><div class="colorpicker-hue"><i></i></div><div class="colorpicker-alpha"><i></i></div><div class="colorpicker-color"><div /></div><div class="colorpicker-selectors"></div></div>',align:"right",customClass:null,colorSelectors:null},d=function(b,d){this.element=a(b).addClass("colorpicker-element"),this.options=a.extend(!0,{},c,this.element.data(),d),this.component=this.options.component,this.component=this.component!==!1&&this.element.find(this.component),this.component&&0===this.component.length&&(this.component=!1),this.container=this.options.container===!0?this.element:this.options.container,this.container=this.container!==!1&&a(this.container),this.input=this.element.is("input")?this.element:!!this.options.input&&this.element.find(this.options.input),this.input&&0===this.input.length&&(this.input=!1),this.color=this.createColor(this.options.color!==!1?this.options.color:this.getValue()),this.format=this.options.format!==!1?this.options.format:this.color.origFormat,this.options.color!==!1&&(this.updateInput(this.color),this.updateData(this.color)),this.disabled=!1;var e=this.picker=a(this.options.template);if(this.options.customClass&&e.addClass(this.options.customClass),this.options.inline?e.addClass("colorpicker-inline colorpicker-visible"):e.addClass("colorpicker-hidden"),this.options.horizontal&&e.addClass("colorpicker-horizontal"),["rgba","hsla","alias"].indexOf(this.format)===-1&&this.options.format!==!1&&"transparent"!==this.getValue()||e.addClass("colorpicker-with-alpha"),"right"===this.options.align&&e.addClass("colorpicker-right"),this.options.inline===!0&&e.addClass("colorpicker-no-arrow"),this.options.colorSelectors){var f=this,g=f.picker.find(".colorpicker-selectors");g.length>0&&(a.each(this.options.colorSelectors,function(b,c){var d=a("<i />").addClass("colorpicker-selectors-color").css("background-color",c).data("class",b).data("alias",b);d.on("mousedown.colorpicker touchstart.colorpicker",function(b){b.preventDefault(),f.setValue("alias"===f.format?a(this).data("alias"):a(this).css("background-color"))}),g.append(d)}),g.show().addClass("colorpicker-visible"))}e.on("mousedown.colorpicker touchstart.colorpicker",a.proxy(function(a){a.target===a.currentTarget&&a.preventDefault()},this)),e.find(".colorpicker-saturation, .colorpicker-hue, .colorpicker-alpha").on("mousedown.colorpicker touchstart.colorpicker",a.proxy(this.mousedown,this)),e.appendTo(this.container?this.container:a("body")),this.input!==!1&&(this.input.on({"keyup.colorpicker":a.proxy(this.keyup,this)}),this.input.on({"input.colorpicker":a.proxy(this.change,this)}),this.component===!1&&this.element.on({"focus.colorpicker":a.proxy(this.show,this)}),this.options.inline===!1&&this.element.on({"focusout.colorpicker":a.proxy(this.hide,this)})),this.component!==!1&&this.component.on({"click.colorpicker":a.proxy(this.show,this)}),this.input===!1&&this.component===!1&&this.element.on({"click.colorpicker":a.proxy(this.show,this)}),this.input!==!1&&this.component!==!1&&"color"===this.input.attr("type")&&this.input.on({"click.colorpicker":a.proxy(this.show,this),"focus.colorpicker":a.proxy(this.show,this)}),this.update(),a(a.proxy(function(){this.element.trigger("create")},this))};d.Color=b,d.prototype={constructor:d,destroy:function(){this.picker.remove(),this.element.removeData("c
1649olorpicker","color").off(".colorpicker"),this.input!==!1&&this.input.off(".colorpicker"),this.component!==!1&&this.component.off(".colorpicker"),this.element.removeClass("colorpicker-element"),this.element.trigger({type:"destroy"})},reposition:function(){if(this.options.inline!==!1||this.options.container)return!1;var a=this.container&&this.container[0]!==window.document.body?"position":"offset",b=this.component||this.element,c=b[a]();"right"===this.options.align&&(c.left-=this.picker.outerWidth()-b.outerWidth()),this.picker.css({top:c.top+b.outerHeight(),left:c.left})},show:function(b){this.isDisabled()||(this.picker.addClass("colorpicker-visible").removeClass("colorpicker-hidden"),this.reposition(),a(window).on("resize.colorpicker",a.proxy(this.reposition,this)),!b||this.hasInput()&&"color"!==this.input.attr("type")||b.stopPropagation&&b.preventDefault&&(b.stopPropagation(),b.preventDefault()),!this.component&&this.input||this.options.inline!==!1||a(window.document).on({"mousedown.colorpicker":a.proxy(this.hide,this)}),this.element.trigger({type:"showPicker",color:this.color}))},hide:function(b){return("undefined"==typeof b||!b.target||!(a(b.currentTarget).parents(".colorpicker").length>0||a(b.target).parents(".colorpicker").length>0))&&(this.picker.addClass("colorpicker-hidden").removeClass("colorpicker-visible"),a(window).off("resize.colorpicker",this.reposition),a(window.document).off({"mousedown.colorpicker":this.hide}),this.update(),void this.element.trigger({type:"hidePicker",color:this.color}))},updateData:function(a){return a=a||this.color.toString(!1,this.format),this.element.data("color",a),a},updateInput:function(a){return a=a||this.color.toString(!1,this.format),this.input!==!1&&(this.input.prop("value",a),this.input.trigger("change")),a},updatePicker:function(a){"undefined"!=typeof a&&(this.color=this.createColor(a));var b=this.options.horizontal===!1?this.options.sliders:this.options.slidersHorz,c=this.picker.find("i");if(0!==c.length)return this.options.horizontal===!1?(b=this.options.sliders,c.eq(1).css("top",b.hue.maxTop*(1-this.color.value.h)).end().eq(2).css("top",b.alpha.maxTop*(1-this.color.value.a))):(b=this.options.slidersHorz,c.eq(1).css("left",b.hue.maxLeft*(1-this.color.value.h)).end().eq(2).css("left",b.alpha.maxLeft*(1-this.color.value.a))),c.eq(0).css({top:b.saturation.maxTop-this.color.value.b*b.saturation.maxTop,left:this.color.value.s*b.saturation.maxLeft}),this.picker.find(".colorpicker-saturation").css("backgroundColor",this.color.toHex(!0,this.color.value.h,1,1,1)),this.picker.find(".colorpicker-alpha").css("backgroundColor",this.color.toHex(!0)),this.picker.find(".colorpicker-color, .colorpicker-color div").css("backgroundColor",this.color.toString(!0,this.format)),a},updateComponent:function(a){var b;if(b="undefined"!=typeof a?this.createColor(a):this.color,this.component!==!1){var c=this.component.find("i").eq(0);c.length>0?c.css({backgroundColor:b.toString(!0,this.format)}):this.component.css({backgroundColor:b.toString(!0,this.format)})}return b.toString(!1,this.format)},update:function(a){var b;return this.getValue(!1)===!1&&a!==!0||(b=this.updateComponent(),this.updateInput(b),this.updateData(b),this.updatePicker()),b},setValue:function(a){this.color=this.createColor(a),this.update(!0),this.element.trigger({type:"changeColor",color:this.color,value:a})},createColor:function(a){return new b(a?a:null,this.options.colorSelectors,this.options.fallbackColor?this.options.fallbackColor:this.color,this.options.fallbackFormat,this.options.hexNumberSignPrefix)},getValue:function(a){a="undefined"==typeof a?this.options.fallbackColor:a;var b;return b=this.hasInput()?this.input.val():this.element.data("color"),void 0!==b&&""!==b&&null!==b||(b=a),b},hasInput:function(){return this.input!==!1},isDisabled:function(){return this.disabled},disable:function(){return this.hasInput()&&this.input.prop("disabled",!0),this.disabled=!0,this.element.trigger({type:"disable",color:this.color,value:this.getValue()}),!0},enable:function(){return this.hasInput()&&this.input.prop("disabled",!1),this.disabled=!1,this.element.trigger({type:"enable",color:this.color,value:this.getValue()}),!0},currentSlider:null,mousePointer:{left:0,top:0},mousedown:function(b){!b.pageX&&!b.pageY&&b.originalEvent&&b.originalEvent.touches&&(b.pageX=b.originalEvent.touches[0].pageX,b.pageY=b.originalEvent.touches[0].pageY),b.stopPropagation(),b.preventDefault();var c=a(b.target),d=c.closest("div"),e=this.options.horizontal?this.options.slidersHorz:this.options.sliders;if(!d.is(".colorpicker")){if(d.is(".colorpicker-saturation"))this.currentSlider=a.extend({},e.saturation);else if(d.is(".colorpicker-hue"))this.currentSlider=a.extend({},e.hue);else{if(!d.is(".colorpicker-alpha"))return!1;this.currentSlider=a.extend({},e.alpha)}var f=d.offset();this.currentSlider.guide=d.find("i")[0].style,this.currentSlider.left=b.pageX-f.left,this.currentSlider.top=b.pageY-f.top,this.mousePointer={left:b.pageX,top:b.pageY},a(window.document).on({"mousemove.colorpicker":a.proxy(this.mousemove,this),"touchmove.colorpicker":a.proxy(this.mousemove,this),"mouseup.colorpicker":a.proxy(this.mouseup,this),"touchend.colorpicker":a.proxy(this.mouseup,this)}).trigger("mousemove")}return!1},mousemove:function(a){!a.pageX&&!a.pageY&&a.originalEvent&&a.originalEvent.touches&&(a.pageX=a.originalEvent.touches[0].pageX,a.pageY=a.originalEvent.touches[0].pageY),a.stopPropagation(),a.preventDefault();var b=Math.max(0,Math.min(this.currentSlider.maxLeft,this.currentSlider.left+((a.pageX||this.mousePointer.left)-this.mousePointer.left))),c=Math.max(0,Math.min(this.currentSlider.maxTop,this.currentSlider.top+((a.pageY||this.mousePointer.top)-this.mousePointer.top)));return this.currentSlider.guide.left=b+"px",this.currentSlider.guide.top=c+"px",this.currentSlider.callLeft&&this.color[this.currentSlider.callLeft].call(this.color,b/this.currentSlider.maxLeft),this.currentSlider.callTop&&this.color[this.currentSlider.callTop].call(this.color,c/this.currentSlider.maxTop),this.options.format!==!1||"setAlpha"!==this.currentSlider.callTop&&"setAlpha"!==this.currentSlider.callLeft||(1!==this.color.value.a?(this.format="rgba",this.color.origFormat="rgba"):(this.format="hex",this.color.origFormat="hex")),this.update(!0),this.element.trigger({type:"changeColor",color:this.color}),!1},mouseup:function(b){return b.stopPropagation(),b.preventDefault(),a(window.document).off({"mousemove.colorpicker":this.mousemove,"touchmove.colorpicker":this.mousemove,"mouseup.colorpicker":this.mouseup,"touchend.colorpicker":this.mouseup}),!1},change:function(a){this.color=this.createColor(this.input.val()),this.color.origFormat&&this.options.format===!1&&(this.format=this.color.origFormat),this.getValue(!1)!==!1&&(this.updateData(),this.updateComponent(),this.updatePicker()),this.element.trigger({type:"changeColor",color:this.color,value:this.input.val()})},keyup:function(a){38===a.keyCode?(this.color.value.a<1&&(this.color.value.a=Math.round(100*(this.color.value.a+.01))/100),this.update(!0)):40===a.keyCode&&(this.color.value.a>0&&(this.color.value.a=Math.round(100*(this.color.value.a-.01))/100),this.update(!0)),this.element.trigger({type:"changeColor",color:this.color,value:this.input.val()})}},a.colorpicker=d,a.fn.colorpicker=function(b){var c=Array.prototype.slice.call(arguments,1
1649),e=1===this.length,f=null,g=this.each(function(){var e=a(this),g=e.data("colorpicker"),h="object"==typeof b?b:{};g||(g=new d(this,h),e.data("colorpicker",g)),"string"==typeof b?a.isFunction(g[b])?f=g[b].apply(g,c):(c.length&&(g[b]=c[0]),f=g[b]):f=e});return e?f:g},a.fn.colorpicker.constructor=d});
1650;
1651(function ($) {
1652
1653  $.fn.rating = function () {
1654
1655    var element;
1656
1657    // A private function to highlight a star corresponding to a given value
1658    function _paintValue(ratingInput, value, active_icon, inactive_icon) {
1659      var selectedStar = $(ratingInput).find('[data-value="' + value + '"]');
1660      selectedStar.removeClass(inactive_icon).addClass(active_icon);
1661      selectedStar.prevAll('[data-value]').removeClass(inactive_icon).addClass(active_icon);
1662      selectedStar.nextAll('[data-value]').removeClass(active_icon).addClass(inactive_icon);
1663    }
1664
1665    // A private function to remove the highlight for a selected rating
1666    function _clearValue(ratingInput, active_icon, inactive_icon) {
1667      var self = $(ratingInput);
1668      self.find('[data-value]').removeClass(active_icon).addClass(inactive_icon);
1669    }
1670
1671    // A private function to change the actual value to the hidden field
1672    function _updateValue(input, val) {
1673      input.val(val).trigger('change');
1674      if (val === input.data('empty-value')) {
1675        input.siblings('.rating-clear').hide();
1676      } else {
1677        input.siblings('.rating-clear').show();
1678      }
1679    }
1680
1681
1682    // Iterate and transform all selected inputs
1683    for (element = this.length - 1; element >= 0; element--) {
1684
1685      var el, i,
1686        originalInput = $(this[element]),
1687        max = originalInput.data('max') || 5,
1688        min = originalInput.data('min') || 0,
1689        def_val = originalInput.val() || 0,
1690        lib = originalInput.data('icon-lib') || 'glyphicon',
1691        active = originalInput.data('active-icon') || 'glyphicon-star',
1692        inactive = originalInput.data('inactive-icon') || 'glyphicon-star-empty',
1693        clearable = originalInput.data('clearable') || null,
1694        clearable_i = originalInput.data('clearable-icon') || 'glyphicon-remove',
1695        stars = '';
1696
1697      // HTML element construction
1698      for (i = min; i <= max; i++) {
1699        // Create <max> empty stars
1700        if(i  <= def_val){
1701          stars += ['<i class="',lib, ' ', active, '" data-value="', i, '"></i>'].join('');
1702          }
1703        else{
1704            stars += ['<i class="',lib, ' ', inactive, '" data-value="', i, '"></i>'].join('')
1705            }
1706      }
1707      // Add a clear link if clearable option is set
1708      if (clearable) {
1709          stars += [
1710          ' <a class="rating-clear" style="display:none;" href="javascript:void">',
1711          '<span class="',lib,' ',clearable_i,'"></span> ',
1712          clearable,
1713          '</a>'].join('');
1714      }
1715
1716      // Clone with data and events the original input to preserve any additional data and event bindings.
1717      var newInput = originalInput.clone(true)
1718        .addClass('hidden')
1719        .data('max', max)
1720        .data('min', min)
1721        .data('icon-lib', lib)
1722        .data('active-icon', active)
1723        .data('inactive-icon', inactive);
1724
1725      // Rating widget is wrapped inside a div
1726      el = [
1727        '<div class="rating-input">',
1728        stars,
1729        '</div>'].join('');
1730
1731      // Replace original inputs HTML with the new one
1732      if (originalInput.parents('.rating-input').length <= 0) {
1733        originalInput.replaceWith($(el).append(newInput));
1734      }
1735
1736    }
1737
1738    // Give live to the newly generated widgets
1739    $('.rating-input')
1740      // Highlight stars on hovering
1741      .on('mouseenter', '[data-value]', function () {
1742        var self = $(this);
1743         input = self.siblings('input');
1744        _paintValue(self.closest('.rating-input'), self.data('value'), input.data('active-icon'), input.data('inactive-icon'));
1745      })
1746      // View current value while mouse is out
1747      .on('mouseleave', '[data-value]', function () {
1748        var self = $(this),
1749          input = self.siblings('input'),
1750          val = input.val(),
1751          min = input.data('min'),
1752          max = input.data('max'),
1753          active = input.data('active-icon'),
1754          inactive = input.data('inactive-icon');
1755        if (val >= min && val <= max) {
1756          _paintValue(self.closest('.rating-input'), val, active, inactive);
1757        } else {
1758          _clearValue(self.closest('.rating-input'), active, inactive);
1759        }
1760      })
1761      // Set the selected value to the hidden field
1762      .on('click', '[data-value]', function (e) {
1763        var self = $(this),
1764          val = self.data('value'),
1765          input = self.siblings('input');
1766        _updateValue(input,val);
1767        e.preventDefault();
1768        return false;
1769      })
1770      // Remove value on clear
1771      .on('click', '.rating-clear', function (e) {
1772        var self = $(this),
1773          input = self.siblings('input'),
1774          active = input.data('active-icon'),
1775          inactive = input.data('inactive-icon');
1776        _updateValue(input, input.data('empty-value'));
1777        _clearValue(self.closest('.rating-input'), active, inactive);
1778        e.preventDefault();
1779        return false;
1780      })
1781      // Initialize view with default value
1782      .each(function () {
1783        var input = $(this).find('input'),
1784          val = input.val(),
1785          min = input.data('min'),
1786          max = input.data('max');
1787        if (val !== "" && +val >= min && +val <= max) {
1788          _paintValue(this, val);
1789          $(this).find('.rating-clear').show();
1790        }
1791        else {
1792          input.val(input.data('empty-value'));
1793          _clearValue(this);
1794        }
1795      });
1796
1797  };
1798
1799  // Auto apply conversion of number fields with class 'rating' into rating-fields
1800  $(function () {
1801    if ($('input.rating[type=number]').length > 0) {
1802      $('input.rating[type=number]').rating();
1803    }
1804  });
1805
1806}(jQuery));
1807;
1808(function ($) {
1809    'use strict';
1810    window.Rock = window.Rock || {};
1811    Rock.controls = Rock.controls || {};
1812
1813    Rock.controls.bootstrapButton = (function () {
1814
1815        var exports = {
1816
1817            showLoading: function (btn) {
1818
1819                if (typeof (Page_ClientValidate) === 'function') {
1820
1821                    if (typeof Page_IsValid === 'undefined' || Page_IsValid) {
1822                        // make sure page really is valid
1823                        Page_ClientValidate();
1824                    }
1825                }
1826
1827                var $btn = $(btn);
1828
1829                if (typeof Page_IsValid === 'undefined' || Page_IsValid) {
1830                    setTimeout(function () {
1831                        $btn.prop('disabled', true);
1832                        $btn.attr('disabled', 'disabled');
1833                        $btn.addClass('disabled');
1834                        $btn.html($btn.attr('data-loading-text'));
1835                    }, 0)
1836                }
1837
1838                return true;
1839            },
1840
1841            onCompleted: function (btn) {
1842                var $btn = $(btn);
1843                $btn.prop('disabled', true);
1844                $btn.attr('disabled', 'disabled');
1845                $btn.addClass('disabled');
1846                var completedTxt = $btn.attr('data-completed-text');
1847                if (completedTxt && completedTxt !== '') {
1848                    $btn.html(completedTxt);
1849                } else {
1850                    $btn.html($btn.attr('data-init-text'));
1851                }
1852
1853                var completedMessage = $btn.attr('data-completed-message');
1854                if (completedMessage && completedMessage !== '') {
1855                    var id = $btn.attr("id") + "_msg";
1856                    var $span = $('<span />').attr('id', id).html(completedMessage);
1857                    $btn.after( $span );
1858                }
1859
1860                var timeout = Number($btn.attr('data-timeout-text'));
1861                if ( timeout && !isNaN(timeout)) {
1862                    timeout = timeout * 1000;
1863                    setTimeout(function () {
1864                        $btn.prop('disabled', false);
1865                        $btn.removeAttr('disabled');
1866                        $btn.removeClass('disabled');
1867                        $btn.html($btn.attr('data-init-text'));
1868                        var id = $btn.attr("id") + "_msg";
1869                        var $span = $('#' + id);
1870                        if ($span){
1871                            $span.remove();
1872                        }
1873                    }, timeout);
1874                }
1875            }
1876        };
1877
1878        return exports;
1879    }());
1880}(jQuery));
1881;
1882(function ($) {
1883    'use strict';
1884    window.Rock = window.Rock || {};
1885    Rock.controls = Rock.controls || {};
1886
1887    Rock.controls.buttonDropDownList = (function () {
1888        var exports = {
1889            initialize: function (options) {
1890                if (!options.controlId) {
1891                    throw 'id is required';
1892                }
1893
1894                var $control = $('#' + options.controlId);
1895                var $selectBtn = $control.find('.js-buttondropdown-btn-select');
1896                var $selectedId = $control.find('.js-buttondropdown-selected-id');
1897                var checkmarksEnabled = $control.attr('data-checkmarks-enabled') == 1;
1898
1899                $('.dropdown-menu a', $control).on('click', function (e) {
1900                    var $el = $(this);
1901                    var text = $el.html();
1902                    var textHtml = $el.html() + " <span class='ti ti-caret-down-filled' ></span >";
1903                    var idValue = $el.attr('data-id');
1904                    var postbackScript = $el.attr('data-postback-script');
1905
1906                    if (checkmarksEnabled) {
1907                        $el.closest('.dropdown-menu').find('.js-selectionicon').removeClass('ti-check');
1908                        $el.find('.js-selectionicon').addClass('ti-check');
1909                    }
1910                    else {
1911                        $selectBtn.html(textHtml);
1912                    }
1913
1914                    $selectedId.val(idValue);
1915
1916                    if (postbackScript) {
1917                        window.location = postbackScript;
1918                    }
1919                });
1920
1921            }
1922        };
1923
1924        return exports;
1925    }());
1926}(jQuery));
1927
1928
1929
1930;
1931(function ($) {
1932    'use strict';
1933    window.Rock = window.Rock || {};
1934    Rock.controls = Rock.controls || {};
1935
1936    Rock.controls.buttonGroup = (function () {
1937        var exports = {
1938            initialize: function (options) {
1939                if (!options.id) {
1940                    throw 'id is required';
1941                }
1942
1943                var $buttonGroup = $('#' + options.id);
1944                var $buttonGroupItems = $buttonGroup.find('.js-buttongroup-item');
1945                var selectedItemClass = $buttonGroup.attr('data-selecteditemclass');
1946                var unselectedItemClass = $buttonGroup.attr('data-unselecteditemclass');
1947
1948                $buttonGroupItems.on('click', function () {
1949                    var $selectedItem = $(this);
1950                    var $unselectedItems = $buttonGroupItems.not($selectedItem);
1951                    $unselectedItems.removeClass(selectedItemClass).addClass(unselectedItemClass);
1952                    $selectedItem.removeClass(unselectedItemClass).addClass(selectedItemClass);
1953
1954                    // make sure the input elements onclick get executed so that the postback gets fired (if there is one)
1955                    var $input = $selectedItem.find('input');
1956                    var clickFunction = $input.prop('onclick');
1957                    if (clickFunction) {
1958                        clickFunction();
1959                    }
1960                });
1961
1962            }
1963        };
1964
1965        return exports;
1966    }());
1967}(jQuery));
1968;
1969(function ($) {
1970    'use strict';
1971    window.Rock = window.Rock || {};
1972    Rock.controls = Rock.controls || {};
1973
1974    Rock.controls.captcha = (function () {
1975
1976        // #region Constants
1977
1978        const CaptchaMode = Object.freeze({
1979            Visible: 0,
1980            Invisible: 1,
1981            Disabled: 2
1982        });
1983
1984        /**
1985         * A map of RockCaptcha controls keyed by their control IDs.
1986         * @type {Object.<string, RockCaptcha>}
1987         */
1988        const captchaControls = {};
1989
1990        // #endregion Constants
1991
1992        // #region Types
1993
1994        /**
1995         * @typedef CaptchaConfigurationBag
1996         * @property {CaptchaMode} captchaMode
1997         */
1998
1999        /**
2000         * @typedef CaptchaInitializeResultBag
2001         * @property {(CaptchaProofOfWorkChallengeBag | null | undefined)} pow Might be null
2002         */
2003
2004        /**
2005         * @typedef CaptchaProofOfWorkChallengeBag
2006         * @property {number} challengeCount
2007         * @property {number} challengeDifficulty
2008         * @property {number} challengeSize
2009         * @property {string} challengeToken
2010         */
2011
2012        /**
2013         * @typedef CaptchaVerifyOptionsBag
2014         * @property {CaptchaProofOfWorkVerifyOptionsBag} powOptions Might be null
2015         */
2016
2017        /**
2018         * @typedef CaptchaProofOfWorkVerifyOptionsBag
2019         * @property {string} challengeToken
2020         * @property {number[]} challengeSolutions
2021         */
2022
2023        /**
2024         * @typedef CaptchaVerifyResultBag
2025         * @property {boolean} isVerified
2026         * @property {string} error Can be null
2027         * @property {number} expires Can be null
2028         * @property {string} token Can be null 
2029         */
2030
2031        /**
2032         * @typedef InitializeConfig
2033         * @property {string} id The ID for the CAPTCHA control.
2034         * @property {string} postBackScript The postback script when a CAPTCHA token has been verified.
2035         */
2036
2037        /**
2038         * The RockCaptcha initialization options.
2039         * @typedef RockCaptchaInitOptions
2040         * @property {string} controlId The control ID for the element (without leading #).
2041         * @property {number} captchaMode The CAPTCHA mode.
2042         * @property {string} postBackScript The script to run when the CAPTCHA token is resolved.
2043         */
2044
2045        /**
2046         * The Api initialization options.
2047         * @typedef ApiInitOptions
2048         * @property {string} baseUrl The base URL for API requests.
2049         */
2050
2051        /**
2052         * @typedef SolveChallengeOptions
2053         * @property {string[]} solutions
2054         * @property {string} token
2055         */
2056
2057        /**
2058         * @typedef CapChallengeResult
2059         * @property {CapChallenge} challenge Might be null
2060         * @property {string} token Might be null
2061         */
2062
2063        /**
2064         * @typedef CapChallenge
2065         * @property {number} c
2066         * @property {number} s
2067         * @property {number} d
2068         */
2069
2070        /**
2071         * @typedef CapRedeemOptions
2072         * @property {number[]} solutions
2073         * @property {string} token
2074         */
2075
2076        /**
2077         * @typedef CapRedeemResult
2078         * @property {boolean} success
2079         * @property {string} message Might be null
2080         * @property {number} expires Might be null
2081         * @property {string} token Might be null
2082         */
2083
2084        /**
2085         * Creates a new instance of the RockCaptcha class.
2086         * @param {RockCaptchaInitOptions} options
2087         */
2088        function RockCaptcha(options) {
2089            if (!options.controlId) {
2090                throw "`controlId` is required.";
2091            }
2092
2093            // Set properties.
2094            /** @type {string} */
2095            this.controlId = options.controlId;
2096
2097            /** @type {CaptchaMode} */
2098            this.captchaMode = options.captchaMode;
2099
2100            /** @type {string} */
2101            this.postBackScript = options.postBackScript;
2102
2103            /** @type {string} */
2104            this.captchaToken = ""; // initialize with an empty CATCHA token.
2105
2106            /** @type {HTMLElement | null} Can be null */
2107            this.widgetEl = null; // initialize the widget element to null.
2108
2109            /** @type {boolean} */
2110            this.disposed = false;
2111
2112            // Set JQuery selectors.
2113            /** @type {JQuery<HTMLElement>} */
2114            this.$controlSelector = $("#" + this.controlId);
2115
2116            /** @type {JQuery<HTMLElement>} */
2117            this.$hfToken = $("#" + this.controlId + '_hfToken');
2118        }
2119
2120        RockCaptcha.prototype.render = async function () {
2121            const areCustomElementsSupported = typeof window.customElements !== "undefined";
2122
2123            if (areCustomElementsSupported && this.captchaMode === CaptchaMode.Visible) {
2124                this.widgetEl = document.createElement("cap-widget");
2125                this.widgetEl.setAttribute("data-cap-api-endpoint", "/"); // This is a required attribute for the library but the endpoint will be overridden later.
2126
2127                // Styles
2128                this.widgetEl.style.setProperty("--cap-background", "var(--color-interface-softest)");
2129                this.widgetEl.style.setProperty("--cap-border-radius", "var(--rounded-small)");
2130                this.widgetEl.style.setProperty("--cap-border-color", "var(--color-interface-soft)");
2131                this.widgetEl.style.setProperty("--cap-widget-padding", "var(--spacing-medium)");
2132                this.widgetEl.style.setProperty("--cap-color", "var(--color-interface-strong)");
2133                this.widgetEl.style.setProperty("--cap-widget-width", "200px");
2134                this.widgetEl.style.setProperty("--cap-checkbox-background", "var(--color-interface-softest)");
2135                this.widgetEl.style.setProperty("--cap-checkbox-border", "1px solid var(--color-interface-soft)");
2136                this.widgetEl.style.setProperty("--cap-checkbox-border-radius", "var(--rounded-xsmall)");
2137                this.widgetEl.style.setProperty("--cap-checkbox-margin", "var(--spacing-large)");
2138                this.widgetEl.style.setProperty("--cap-spinner-background-color", "var(--color-interface-soft)");
2139                this.widgetEl.style.setProperty("--cap-spinner-color", "var(--color-primary)");
2140                this.widgetEl.style.setProperty("--cap-spinner-thickness", "6px");
2141                this.widgetEl.style.setProperty("--cap-gap", "var(--spacing-medium)");
2142                this.widgetEl.style.setProperty("--cap-font", "var(--font-family-body)");
2143                this.widgetEl.style.setProperty("--cap-checkbox-size", "20px");
2144
2145                this.widgetEl.addEventListener("solve", async (e) => {
2146                    if (this.disposed) {
2147                        return;
2148                    }
2149
2150                    const token = e && e.detail && e.detail.token ? e.detail.token : undefined;
2151
2152                    this.handleSolvedToken(token);
2153                });
2154
2155                this.widgetEl.addEventListener("error", (_e) => {
2156                    if (this.disposed) {
2157                        return;
2158                    }
2159
2160                    this.handleError("Captcha challenge failed.");
2161                });
2162
2163                const host = this.$controlSelector[0];
2164                if (host) {
2165                    host.appendChild(this.widgetEl);
2166
2167                    setTimeout(() => {
2168                        if (this.widgetEl && this.widgetEl.shadowRoot) {
2169                            const attribution = this.widgetEl.shadowRoot.querySelector('[part="attribution"]');
2170                            if (attribution) {
2171                                attribution.remove();
2172                            }
2173                        }
2174                    })
2175                }
2176
2177                return;
2178            }
2179
2180            // Invisible mode fallback (older Safari/Edge without customElements)
2181            try {
2182                const cap = new Cap();
2183                const token = await cap.solve();
2184                if (!this.disposed) {
2185                    await this.handleSolvedToken(token.token);
2186                }
2187            }
2188            catch {
2189                if (!this.disposed) {
2190                    this.handleError("Captcha challenge failed.");
2191                }
2192            }
2193        };
2194
2195        /**
2196         * Handles processing a CAPTCHA token after it is solved.
2197         * @param {string} token
2198         */
2199        RockCaptcha.prototype.handleSolvedToken = async function (token) {
2200            this.captchaToken = token;
2201            this.$hfToken.val(token);
2202
2203            // Notify the page that the CAPTCHA has been solved so client code can
2204            // react (e.g., reveal a Next button) without needing a server postback.
2205            // Fires for both Visible and Invisible modes.
2206            this.$controlSelector.trigger('rockcaptcha:solved', [{ token: token }]);
2207
2208            if (this.postBackScript) {
2209                window.location = "javascript:" + this.postBackScript;
2210            }
2211        };
2212
2213        /**
2214         * Handles an error.
2215         * @param {string} msg
2216         */
2217        RockCaptcha.prototype.handleError = function (msg) {
2218            console.error(msg);
2219            this.captchaToken = "";
2220        };
2221
2222        /**
2223          * Creates a new instance of the Api class.
2224          * @param {ApiInitOptions} options
2225          */
2226        function Api(options) {
2227            this.baseUrl = options.baseUrl;
2228        }
2229
2230        /**
2231         * Gets the CAPTCHA configuration.
2232         * @returns {Promise<CaptchaConfigurationBag>}
2233         */
2234        Api.prototype.getConfiguration = async function () {
2235            return await Promise.resolve($.post({
2236                url: this.baseUrl + 'api/v2/Controls/CaptchaGetConfiguration'
2237            }));
2238        };
2239
2240        /**
2241         * Initializes the CAPTCHA.
2242         * @returns {Promise<CaptchaInitializeResultBag>}
2243         */
2244        Api.prototype.initialize = async function () {
2245            return await Promise.resolve($.post({
2246                url: this.baseUrl + 'api/v2/Controls/CaptchaInitialize'
2247            }));
2248        };
2249
2250        /**
2251         * Verifies the CAPTCHA.
2252         * @param {CaptchaVerifyOptionsBag} options
2253         * @returns {Promise<CaptchaVerifyResultBag>}
2254         */
2255        Api.prototype.verify = async function (options) {
2256            return await Promise.resolve($.post({
2257                url: this.baseUrl + 'api/v2/Controls/CaptchaVerify',
2258                data: options
2259            }));
2260        };
2261
2262        // #endregion Types
2263
2264        /**
2265         * Gets whether the Cap module is ready.
2266         * 
2267         * @returns Whether the Cap module is ready.
2268         */
2269        function isCapReady() {
2270            return typeof Cap !== 'undefined';
2271        }
2272
2273        const api = new Api({
2274            baseUrl: '/'
2275        });
2276
2277        async function loadCap() {
2278            if (typeof window.Cap !== "undefined") {
2279                return;
2280            }
2281            else {
2282                $.ajaxSetup({ cache: true });
2283                return await Promise.resolve($.getScript("https://cdn.jsdelivr.net/npm/@cap.js/[email protected]"));
2284            }
2285        }
2286
2287        var exports = {
2288            /**
2289             * Initializes a new RockCaptcha instance.
2290             * @param {InitializeConfig} config
2291             */
2292            initialize: async function (config) {
2293                if (!config) {
2294                    return;
2295                }
2296
2297                if (!config.id) {
2298                    throw 'id is required';
2299                }
2300
2301                function freezeProp(target, propertyName, value) {
2302                    Object.defineProperty(target, propertyName, {
2303                        get: () => value,
2304                        set: undefined,
2305                        configurable: false,
2306                        enumerable: true
2307                    });
2308                }
2309
2310                // Override the fetch function used by Cap to route requests via the Rock captcha API.
2311                if (!window.CAP_CUSTOM_FETCH) {
2312                    freezeProp(window, "CAP_CUSTOM_FETCH", async function (url, options) {
2313                        if (typeof url === "string") {
2314                            if (url.endsWith("/challenge")) {
2315                                const result = await api.initialize();
2316
2317                                // Format the challenge as expected by Cap.js
2318
2319                                /** @type {CapChallengeResult} */
2320                                const capResult = {
2321                                    challenge: null,
2322                                    token: null
2323                                };
2324
2325                                if (result && result.pow) {
2326                                    capResult.token = result.pow.challengeToken;
2327                                    capResult.challenge = {
2328                                        c: result.pow.challengeCount,
2329                                        s: result.pow.challengeSize,
2330                                        d: result.pow.challengeDifficulty
2331                                    };
2332                                }
2333
2334                                return Response.json(capResult);
2335                            }
2336                            else if (url.endsWith("/redeem")) {
2337                                /** @type {CapRedeemOptions} */
2338                                const capRedeemOptions = options && options.body ? JSON.parse(options.body.toString()) : null;
2339
2340                                /** @type {CaptchaVerifyOptionsBag} */
2341                                const verifyOptions = {
2342                                    powOptions: null
2343                                };
2344
2345                                if (capRedeemOptions) {
2346                                    verifyOptions.powOptions = {
2347                                        challengeSolutions: capRedeemOptions.solutions,
2348                                        challengeToken: capRedeemOptions.token
2349                                    };
2350                                }
2351
2352                                const result = await api.verify(verifyOptions);
2353
2354                                /** @type {CapRedeemResult} */
2355                                const capResult = {
2356                                    success: false,
2357                                    expires: null,
2358                                    message: "An error occurred",
2359                                    token: null
2360                                };
2361
2362                                if (result) {
2363                                    capResult.success = result.isVerified;
2364                                    capResult.message = result.error;
2365                                    capResult.expires = result.expires;
2366                                    capResult.token = result.token;
2367                                };
2368
2369                                return Response.json(capResult);
2370                            }
2371                        }
2372
2373                        return fetch(url, options);
2374                    });
2375                }
2376
2377                const rockCaptcha = captchaControls[config.id];
2378                if (rockCaptcha) {
2379                    if (rockCaptcha.widgetEl) {
2380                        rockCaptcha.widgetEl.remove();
2381                    }
2382                }
2383
2384                await loadCap();
2385                const captchaConfig = await api.getConfiguration();
2386
2387                // Store a ref to the new control so it can be managed later.
2388                captchaControls[config.id] = new RockCaptcha({
2389                    api: api,
2390                    captchaMode: captchaConfig.captchaMode,
2391                    controlId: config.id,
2392                    postBackScript: config.postBackScript
2393                });
2394
2395                $(document).ready(async function () {
2396                    await captchaControls[config.id].render();
2397                });
2398            },
2399            clientValidate: function (validator, args) {
2400                var $validator = $(validator);
2401                var isRequired = $validator.data('required');
2402
2403                var rockCaptchaId = $validator.data('captcha-id');
2404                var $formGroup = $('#' + rockCaptchaId).closest('.form-group');
2405
2406                if (isRequired && isCapReady()) {
2407                    // Get this validator control's related Rock Captcha control ID and
2408                    // try to find the underlying Turnstile widget.
2409                    var turnstileId = captchaIds[rockCaptchaId];
2410                    if (turnstileId) {
2411                        var turnstileResponse = turnstile.getResponse(turnstileId);
2412                    }
2413                }
2414
2415                var isValid = !!(!isRequired || turnstileResponse);
2416
2417                if (isValid) {
2418                    args.IsValid = true;
2419                    $formGroup.removeClass('has-error');
2420                }
2421                else {
2422                    args.IsValid = false;
2423                    $formGroup.addClass('has-error');
2424                }
2425            }
2426        };
2427
2428        return exports;
2429    }());
2430}(jQuery));
2431;
2432(function ($) {
2433    'use strict';
2434    window.Rock = window.Rock || {};
2435
2436    Rock.controls.charts = (function () {
2437        var exports = {
2438
2439            ///
2440            /// handles putting chartData into a Line/Bar/Points chart
2441            ///
2442            plotChartData: function (chartData, chartOptions, plotSelector, yaxisLabelText, getSeriesPartitionNameUrl, combineValues) {
2443
2444                var chartSeriesLookup = {};
2445                var chartSeriesList = [];
2446
2447                var chartGoalPoints = {
2448                    label: yaxisLabelText + ' Goal',
2449                    chartData: [],
2450                    data: []
2451                }
2452
2453                if (chartOptions.customSettings) {
2454                    chartGoalPoints.color = chartOptions.customSettings.goalSeriesColor;
2455                }
2456
2457                // populate the chartMeasurePoints data array with data from the REST result
2458                for (var i = 0; i < chartData.length; i++) {
2459                    if (chartData[i].MetricValueType && chartData[i].MetricValueType == 1) {
2460                        // if the chartdata is marked as a Metric Goal Value Type, populate the chartGoalPoints
2461                        chartGoalPoints.data.push([chartData[i].DateTimeStamp, chartData[i].YValue]);
2462                        chartGoalPoints.chartData.push(chartData[i]);
2463                    }
2464                    else {
2465                        var lookupKey = chartData[i].MetricValuePartitionEntityIds;
2466                        if (!lookupKey || lookupKey == '') {
2467                            lookupKey = chartData[i].SeriesName;
2468                        }
2469
2470                        if (!chartSeriesLookup[lookupKey]) {
2471
2472                            // If SeriesName is specified, that can be the name of the series if MetricValuePartitionEntityIds is blank
2473                            var seriesName = chartData[i].SeriesName;
2474
2475                            // if we aren't combining values, we'll have to lookup the series n
2475ame based on MetricValuePartitionEntityIds
2476                            if (chartData[i].MetricValuePartitionEntityIds && chartData[i].MetricValuePartitionEntityIds != '' && !combineValues)
2477                            {
2478                                // MetricValuePartitionEntityIds is not blank so get the seriesName from the getSeriesPartitionNameUrl
2479                                if (getSeriesPartitionNameUrl) {
2480                                    var metricValuePartitionEntityIdDataJSON = JSON.stringify(chartData[i].MetricValuePartitionEntityIds.split(','));
2481
2482                                    $.ajax({
2483                                        type: 'POST',
2484                                        url: getSeriesPartitionNameUrl,
2485                                        data: metricValuePartitionEntityIdDataJSON,
2486                                        async: false,
2487                                        success: null,
2488                                        contentType: 'application/json'
2489                                    })
2490                                    .done(function (data) {
2491                                        seriesName = data;
2492                                    });
2493                                }
2494                            }
2495
2496                            // either either seriesName, yaxisLabelText or just 'value' if the seriesname isn't defined
2497                            seriesName = seriesName || yaxisLabelText || 'value';
2498
2499                            chartSeriesLookup[lookupKey] = {
2500                                label: seriesName,
2501                                chartData: [],
2502                                data: []
2503                            };
2504                        }
2505
2506                        chartSeriesLookup[lookupKey].data.push([chartData[i].DateTimeStamp, chartData[i].YValue]);
2507                        chartSeriesLookup[lookupKey].chartData.push(chartData[i]);
2508                    }
2509                }
2510
2511                // setup the series list (goal points last, if they exist)
2512                for (var chartSeriesKey in chartSeriesLookup) {
2513                    var chartMeasurePoints = chartSeriesLookup[chartSeriesKey];
2514                    if (chartMeasurePoints.data.length) {
2515                        chartSeriesList.push(chartMeasurePoints);
2516                    }
2517                }
2518
2519                if (combineValues && chartSeriesList.length != 1) {
2520
2521                    var combinedDataLookup = {};
2522                    var chartMeasurePoints = null;
2523
2524                    for (var chartMeasurePointsId in chartSeriesList) {
2525
2526                        chartMeasurePoints = chartSeriesList[chartMeasurePointsId];
2527
2528                        $.each(chartMeasurePoints.data, function (indexInArray, dataPair) {
2529                            var dateTimeIndex = dataPair[0];
2530                            var combinedItem = combinedDataLookup[dateTimeIndex];
2531                            if (combinedItem) {
2532                                // sum the YValue of the .data
2533                                combinedItem.chartData.YValue += chartMeasurePoints.chartData[indexInArray].YValue;
2534                                combinedItem.data[1] += dataPair[1];
2535                            }
2536                            else {
2537                                combinedDataLookup[dateTimeIndex] = {
2538                                    chartData: chartMeasurePoints.chartData[indexInArray],
2539                                    data: dataPair
2540                                };
2541                            }
2542                        });
2543                    }
2544
2545                    var combinedData = [];
2546                    var combinedChartData = [];
2547
2548                    for (var dataLookupId in combinedDataLookup) {
2549                        combinedData.push(combinedDataLookup[dataLookupId].data);
2550                        combinedChartData.push(combinedDataLookup[dataLookupId].chartData);
2551                    }
2552
2553                    chartSeriesList = [];
2554
2555                    // sort data after combining
2556                    combinedChartData.sort(function (item1, item2) { return item2.DateTimeStamp - item1.DateTimeStamp });
2557                    combinedData.sort(function (item1, item2) { return item2[0] - item1[0]; });
2558
2559                    var chartCombinedMeasurePoints = {
2560                        label: yaxisLabelText + ' Total',
2561                        chartData: combinedChartData,
2562                        data: combinedData
2563                    };
2564
2565
2566                    chartSeriesList.push(chartCombinedMeasurePoints);
2567                }
2568
2569                if (chartGoalPoints.data.length) {
2570                    chartSeriesList.push(chartGoalPoints);
2571                }
2572
2573                // plot the chart
2574                if (chartSeriesList.length > 0) {
2575                    $.plot(plotSelector, chartSeriesList, chartOptions);
2576                }
2577                else {
2578                    $(plotSelector).html('<div class="alert alert-info">No Data Found</div>');
2579                }
2580            },
2581
2582            ///
2583            /// handles putting chart data into a piechart
2584            ///
2585            plotPieChartData: function (chartData, chartOptions, plotSelector, getSeriesPartitionNameUrl) {
2586                var pieData = [];
2587
2588                // populate the chartMeasurePoints data array with data from the REST result for pie data
2589                for (var i = 0; i < chartData.length; i++) {
2590
2591                    pieData.push({
2592                        label: chartData[i].MetricTitle,
2593                        data: chartData[i].YValueTotal,
2594                        chartData: [chartData[i]]
2595                    });
2596                }
2597
2598                if (pieData.length > 0) {
2599                    // plot the pie chart
2600                    $.plot(plotSelector, pieData, chartOptions);
2601                }
2602                else {
2603                    $(plotSelector).html('<div class="alert alert-info">No Data Found</div>');
2604                }
2605            },
2606
2607            ///
2608            /// handles putting chart data into a bar chart
2609            ///
2610            plotBarChartData: function (chartData, chartOptions, plotSelector, getSeriesPartitionNameUrl) {
2611                var barData = [];
2612                var combinedData = {};
2613                var seriesLabels = [];
2614                var seriesNameLookup = {};
2615
2616                // populate the chartMeasurePoints data array with data from the REST result for bar chart data
2617                for (var i = 0; i < chartData.length; i++) {
2618
2619                    var seriesCategory = chartData[i].SeriesName;
2620                    if (chartData[i].MetricValuePartitionEntityIds)
2621                    {
2622                        if (!seriesNameLookup[chartData[i].MetricValuePartitionEntityIds])
2623                        {
2624                            // MetricValuePartitionEntityIds is not blank so get the seriesName from the getSeriesPartitionNameUrl
2625                            if (getSeriesPartitionNameUrl) {
2626                                var metricValuePartitionEntityIdDataJSON = JSON.stringify(chartData[i].MetricValuePartitionEntityIds.split(','));
2627
2628                                $.ajax({
2629                                    type: 'POST',
2630                                    url: getSeriesPartitionNameUrl,
2631                                    data: metricValuePartitionEntityIdDataJSON,
2632                                    async: false,
2633                                    success: null,
2634                                    contentType: 'application/json'
2635                                })
2636                                .done(function (data) {
2637                                    seriesNameLookup[chartData[i].MetricValuePartitionEntityIds] = data;
2638                                });
2639                            }
2640                        }
2641
2642                        seriesCategory = seriesNameLookup[chartData[i].MetricValuePartitionEntityIds] || seriesCategory;
2643                    }
2644
2645                    if (!combinedData[seriesCategory])
2646                    {
2647                        combinedData[seriesCategory] = 0;
2648                    }
2649
2650                    combinedData[seriesCategory] += chartData[i].YValue;
2651                }
2652
2653                var seriesId = 0;
2654                for (var combinedDataKey in combinedData) {
2655
2656                    barData.push([combinedDataKey, combinedData[combinedDataKey]]);
2657                    seriesId++;
2658                    seriesLabels.push(combinedDataKey);
2659                }
2660
2661                seriesLabels.push(seriesCategory);
2662
2663                if (barData.length > 0) {
2664                    // plot the bar chart
2665                    chartOptions.series.chartData = chartData;
2666                    chartOptions.series.labels = seriesLabels;
2667                    $.plot(plotSelector, [barData], chartOptions);
2668                }
2669                else {
2670                    $(plotSelector).html('<div class="alert alert-info">No Data Found</div>');
2671                }
2672            },
2673
2674            ///
2675            /// attaches a bootstrap style tooltip to the 'plothover' of a chart
2676            ///
2677            bindTooltip: function (plotContainerId, tooltipFormatter) {
2678                // setup of bootstrap tooltip which we'll show on the plothover event
2679                var toolTipId = 'tooltip_' + plotContainerId;
2680                var chartContainer = '#' + plotContainerId;
2681
2682                $("<div id=" + toolTipId + " class='tooltip top'><div class='tooltip-inner'></div><div class='tooltip-arrow'></div></div>").css({
2683                    position: 'absolute',
2684                    display: 'none',
2685                }).appendTo('body');
2686
2687                var $toolTip = $('#' + toolTipId);
2688
2689                $(chartContainer).bind('plothover', function (event, pos, item) {
2690
2691                    if (item) {
2692
2693                        var tooltipText = "";
2694
2695                        // if a tooltipFormatter is specified, use that
2696                        if (tooltipFormatter) {
2697                            tooltipText = tooltipFormatter(item);
2698                        }
2699                        else {
2700                            if (item.series.chartData) {
2701                                if (item.series.chartData[item.dataIndex].DateTimeStamp) {
2702                                    tooltipText = new Date(item.series.chartData[item.dataIndex].DateTimeStamp).toLocaleDateString();
2703                                };
2704
2705                                if (item.series.chartData[item.dataIndex].StartDateTimeStamp) {
2706                                    tooltipText = new Date(item.series.chartData[item.dataIndex].StartDateTimeStamp).toLocaleDateString();
2707                                }
2708
2709                                if (item.series.chartData[item.dataIndex].EndDateTimeStamp) {
2710                                    tooltipText += " to " + new Date(item.series.chartData[item.dataIndex].EndDateTimeStamp).toLocaleDateString();
2711                                }
2712
2713                                if (item.series.chartData[item.dataIndex].MetricTitle) {
2714                                    tooltipText = item.series.chartData[item.dataIndex].MetricTitle;
2715                                }
2716                            }
2717
2718                            if (tooltipText) {
2719                                tooltipText += '<br />';
2720                            }
2721
2722                            if (item.series.label) {
2723                                tooltipText += item.series.label;
2724                            }
2725
2726                            if (item.series.chartData) {
2727                                var pointValue = item.series.chartData[item.dataIndex].YValueFormatted || item.series.chartData[item.dataIndex].YValue || item.series.chartData[item.dataIndex].YValueTotal || '';
2728
2729                                tooltipText += ': ' + pointValue;
2730
2731                                if (item.series.chartData[item.dataIndex].Note) {
2732                                    tooltipText += '<br />' + item.series.chartData[item.dataIndex].Note;
2733                                }
2734                            }
2735                        }
2736
2737                        $toolTip.find('.tooltip-inner').html(tooltipText);
2738
2739                        var tipTop = pos.pageY - $toolTip.height() - 10;
2740
2741                        var windowWidth = $(window).width();
2742                        var tooltipWidth = $toolTip.width();
2743                        var tipLeft = pos.pageX - ( tooltipWidth / 2);
2744                        if (tipLeft + tooltipWidth + 10 >= windowWidth) {
2745                            tipLeft = tipLeft - (tooltipWidth / 2);
2746                            $toolTip.removeClass("top");
2747                            $toolTip.addClass("left");
2748                        }
2749                        else {
2750                            $toolTip.removeClass("left");
2751                            $toolTip.addClass("top");
2752                        }
2753
2754                        $toolTip.css({ top: tipTop, left: tipLeft, opacity: 1 });
2755                        $toolTip.show();
2756                    }
2757                    else {
2758                        $toolTip.hide();
2759                    }
2760                });
2761
2762            }
2763        };
2764
2765        return exports;
2766    }());
2767}(jQuery));
2768
2769
2770;
2771(function ($) {
2772    'use strict';
2773    window.Rock = window.Rock || {};
2774    Rock.controls = Rock.controls || {};
2775
2776    Rock.controls.colorSelector = (function () {
2777
2778        // #region Utilities
2779
2780        var ColorUtilities = (function () {
2781            /**
2782             * Detects if two RGB colors are similar.
2783             * @param r1 {number} Red 1 color (0-255).
2784             * @param g1 {number} Green 1 color (0-255).
2785             * @param b1 {number} Blue 1 color (0-255).
2786             * @param r2 {number} Red 2 color (0-255).
2787             * @param g2 {number} Green 2 color (0-255).
2788             * @param b2 {number} Blue 2 color (0-255).
2789             * @param percentSimilar {number|undefined} The percent (0 - 1) that determines if the colors are similar. 
2790             * @returns {Boolean} `true` if the numbers are similar; otherwise, `false`.
2791             */
2792            function areColorsSimilar(r1, g1, b1, r2, g2, b2, percentSimilar) {
2793                if (typeof percentSimilar === "undefined") {
2794                    percentSimilar = 0.99;
2795                }
2796
2797                var actualDistance = getColorDistance(r1, g1, b1, r2, g2, b2);
2798
2799                var actualPercent = (maxLumaDistance - actualDistance) / maxLumaDistance;
2800
2801                // Use the specified percent to determine if the colors are similar.
2802                return actualPercent >= percentSimilar;
2803            }
2804
2805            /**
2806             * Gets the distance between two RGB colors.
2807             * @param r1 {number} Red 1 color (0-255).
2808             * @param g1 {number} Green 1 color (0-255).
2809             * @param b1 {number} Blue 1 color (0-255).
2810             * @param r2 {number} Red 2 color (0-255).
2811             * @param g2 {number} Green 2 color (0-255).
2812             * @param b2 {number} Blue 2 color (0-255).
2813             * @returns {number} The distance between two RGB colors.
2814             */
2815            function getColorDistance(r1, g1, b1, r2, g2, b2) {
2816                var redDiffSquared = Math.pow(r2 - r1, 2);
2817                var greenDiffSquared = Math.pow(g2 - g1, 2);
2818                var blueDiffSquared = Math.pow(b2 - b1, 2);
2819                var redAverage = (r2 + r1) / 2;
2820
2821                return Math.sqrt(
2822                    (2 * redDiffSquared) +
2823                    (4 * greenDiffSquared) +
2824                    (3 * blueDiffSquared) +
2825                    (redAverage * (redDiffSquared - blueDiffSquared) / 256)
2826                );
2827            }
2828
2829            /**
2830             * Converts a hex color (#FFFFFF) to an RGB color. 
2831             * @param {string} hexValue The hex color to convert.
2832             * @returns {Number[]} The converted RGB color.
2833             */
2834            function hexToRgba(hexValue) {
2835                var hex3Or4 = /^#[0-9a-fA-F]{3}([0-9a-fA-F])?$/;
2836                if (hex3Or4.test(hexValue)) {
2837                    var rHex = hexValue[1];
2838                    var gHex = hexValue[2];
2839                    var bHex = hexValue[3];
2840                    var aHex = hexValue[4] || "f";
2841                    // A 3-digit hex color #123 is equivalent to the 6-digit hex color #112233,
2842                    // where each color channel (r, g, and b) is repeated.
2843                    return [
2844                        parseInt(rHex + rHex, 16),
2845                        parseInt(gHex + gHex, 16),
2846                        parseInt(bHex + bHex, 16),
2847                        parseInt(aHex + aHex, 16)
2848                    ];
2849                }
2850
2851                var hex6Or8 = /^#[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$/;
2852                if (hex6Or8.test(hexValue)) {
2853                    var rHex = hexValue.substring(1, 3);
2854                    var gHex = hexValue.substring(3, 5);
2855                    var bHex = hexValue.substring(5, 7);
2856                    var aHex = hexValue.substring(7) || "ff";
2857                    return [
2858                        parseInt(rHex, 16),
2859                        parseInt(gHex, 16),
2860                        parseInt(bHex, 16),
2861                        Math.round(parseInt(aHex, 16) / 255)
2862                    ];
2863                }
2864
2865                // Return transparent if not a valid hex color.
2866                return [0, 0, 0, 0];
2867            }
2868
2869            /**
2870             * Returns the perceived brightness of an RGB color.
2871             * @param r {number} Red color (0-255).
2872             * @param g {number} Green color (0-255).
2873             * @param b {number} Blue color (0-255).
2874             * @returns {Number[]}
2875             */
2876            function rgbToLuma(r, g, b) {
2877                /**
2878                 * To get a color's perceived brightness,
2879                 * each color component is multiplied by a different constant.
2880                 * The constants are weighted differently based on the perceived brightness by the human eye
2881                 * --green is perceived as the brightest color, then red, then blue (they all add up to 1).
2882                 *
2883                 * The result will be a color in the color space:
2884                 * [0, 0, 0] to [0.2126 * 255, 0.7152 * 255, 0.0722 * 255]
2885                 * or...
2886                 * [0, 0, 0] to [54.213, 182.376, 18.411]
2887                 */
2888
2889                return [0.2126 * r, 0.7152 * g, 0.0722 * b];
2890            }
2891
2892            /**
2893             * Returns the RGBA number array from a CSS "rgba(r, g, b, a)" string.
2894             * @param rgbFunc {string} The "rgb(r, g, b)" string.
2895             * @returns {Number[]} An RGBA array [r, b, b, a].
2896             */
2897            function rgbFuncToRgba(rgbFunc) {
2898                var rgb = /^rgb[a]?\s*\(\s*(\d+)[, ]\s*(\d+)[, ]\s*(\d+)\)$/;
2899                var rgbMatches = rgbFunc.match(rgb);
2900                if (rgbMatches && rgbMatches.length) {
2901                    return [
2902                        parseInt(rgbMatches[1]),
2903                        parseInt(rgbMatches[2]),
2904                        parseInt(rgbMatches[3]),
2905                        1
2906                    ];
2907                }
2908
2909                var rgba = /^rgb[a]?\s*\(\s*(\d+)[, ]\s*(\d+)[, ]\s*(\d+)[, ]\s*(\d*(\.\d+)?)\)$/;
2910                var rgbaMatches = rgbFunc.match(rgba);
2911                if (rgbaMatches && rgbMatches.length) {
2912                    return [
2913                        parseInt(rgbaMatches[1]),
2914                        parseInt(rgbaMatches[2]),
2915                        parseInt(rgbaMatches[3]),
2916                        Number(rgbaMatches[4])
2917                    ];
2918                }
2919
2920                // Return transparent by default.
2921                return [0, 0, 0, 0];
2922            }
2923
2924            return {
2925                areColorsSimilar,
2926                getColorDistance,
2927                hexToRgba,
2928                rgbToLuma,
2929                rgbFuncToRgba
2930            }
2931        })();
2932
2933        // #endregion
2934
2935        // #region Types
2936
2937        /**
2938         * The ColorSelector initialization options.
2939         * @typedef {Object} ColorSelectorInitOptions
2940         * @property {string} controlId The control ID for the color selector (without leading #).
2941         * @property {boolean} allowMultiple When true, the Color Selector will behave like a checkbox list; otherwise, it will behave like a radiobutton list.
2942         */
2943
2944        // #endregion 
2945
2946        // #region Constants
2947
2948        var colorSimilarityPercentage = Object.freeze(0.95);
2949        var maxLuma = Object.freeze(ColorUtilities.rgbToLuma(255, 255, 255));
2950        var maxLumaDistance = Object.freeze(ColorUtilities.getColorDistance(maxLuma[0], maxLuma[1], maxLuma[2], 0, 0, 0));
2951        var camouflagedLightClass = Object.freeze("camouflaged-light");
2952        var camouflagedDarkClass = Object.freeze("camouflaged-dark");
2953
2954        // #endregion
2955
2956        // #region Classes
2957
2958        // #region Constructor
2959
2960        /**
2961         * Creates a new instance of the ColorSelector class.
2962         * @param {ColorSelectorInitOptions} options
2963         */
2964        function ColorSelector(options) {
2965            if (!options.controlId) {
2966                throw "`controlId` is required.";
2967            }
2968
2969            this.allowMultiple = !!options.allowMultiple;
2970            this.controlId = options.controlId;
2971
2972            // Set JQuery selectors.
2973            this.$colorSelector = $("#" + this.controlId);
2974            this.$colorSelectorItemContainers = this.$colorSelector.children(".checkbox");
2975
2976            // Set defaults for background color and perceived brightness.
2977            // These will be overwritten during initialization.
2978            this.backgroundColor = [255, 255, 255];
2979            this.backgroundLuma = maxLuma;
2980        }
2981
2982        // #endregion
2983
2984        // #region Methods
2985
2986        /**
2987         * Initializes the color selector.
2988         */
2989        ColorSelector.prototype.initialize = function () {
2990            var colorSelectorElement = this.$colorSelector[0];
2991
2992            if (!colorSelectorElement) {
2993                throw "Unable to find color selector element with control ID " + this.controlId;
2994            }
2995
2996            // Set the background color of the color selector.
2997            this.backgroundColor = getInheritedBackgroundColor(colorSelectorElement);
2998
2999            // Set the perceived brightness of the color picker.
3000            // The perceived brightness is better for determining
3001            // when two colors are similar to the human eye
3002            // instead of comparing RGB values.
3003            this.backgroundLuma = ColorUtilities.rgbToLuma(this.backgroundColor[0], this.backgroundColor[1], this.backgroundColor[2]);
3004
3005            var $colorSelectorItemContainers = this.$colorSelectorItemContainers;
3006
3007            // Initialize the color items.
3008            var checkedFound = false;
3009            for (var i = 0; i < $colorSelectorItemContainers.length; i++) {
3010                var $colorSelectorItemContainer = $($colorSelectorItemContainers[i]);
3011
3012                // If only one selection is allowed, then only leave the first checked input as checked,
3013                // and uncheck the others.
3014                if (!this.allowMultiple) {
3015                    var $input = findInput($colorSelectorItemContainer);
3016
3017                    if ($input.prop("checked")) {
3018                        if (checkedFound) {
3019                            // A checked input was already found so mark this input as unchecked.
3020                            $input.prop("checked", false);
3021                        }
3022
3023                        checkedFound = true;
3024                    }
3025                }
3026
3027                initializeItem(this, $colorSelectorItemContainer);
3028            }
3029        };
3030
3031        // #endregion
3032
3033        // #region Private Functions
3034
3035        /**
3036         * Finds the checkbox input for a color selector item container.
3037         * @param {JQuery<HTMLElement>} $colorSelectorItemContainer
3038         * @returns {JQuery<HTMLInputElement>}
3039         */
3040        function findInput($colorSelectorItemContainer) {
3041            return $colorSelectorItemContainer.find('input[type="checkbox"]');
3042        }
3043
3044        /**
3045         * Initializes the color items.
3046         * @param {ColorSelector} colorSelector
3047         * @param {JQuery<HTMLElement>} $colorSelectorItemContainer
3048         */
3049        function initializeItem(colorSelector, $colorSelectorItemContainer) {
3050            $colorSelectorItemContainer.addClass("color-selector-item-container");
3051
3052            var $checkboxLabel = $colorSelectorItemContainer.find("label");
3053            $checkboxLabel.addClass("color-selector-item");
3054
3055            var $checkboxInput = findInput($colorSelectorItemContainer);
3056            $checkboxInput.addClass("color-selector-item-checkbox");
3057
3058            styleInput($checkboxInput);
3059
3060            // Update the checked state whenever it changes.
3061            $checkboxInput.on("click", function () {
3062                onCheckboxCheckChanged(colorSelector, $(this));
3063            });
3064
3065            // Remove the .checkbox CSS class as we don't want any checkbox styling.
3066            $colorSelectorItemContainer.removeClass("checkbox");
3067
3068            // Set the label background color to the color found in the label text.
3069            var $labelText = $checkboxLabel.find(".label-text");
3070            var itemHexColor = $labelText.text();
3071            $checkboxLabel.css("background-color", itemHexColor);
3072
3073            // Remove the label text so just the color is displayed.
3074            $labelText.remove();
3075
3076            // Determine if the item needs a border to separate it from the background.
3077            var itemRgb = ColorUtilities.hexToRgba(itemHexColor);
3078            var itemLuma = ColorUtilities.rgbToLuma(itemRgb[0], itemRgb[1], itemRgb[2]);
3079
3080            if (ColorUtilities.areColorsSimilar(colorSelector.backgroundLuma[0], colorSelector.backgroundLuma[1], colorSelector.backgroundLuma[2], itemLuma[0], itemLuma[1], itemLuma[2], colorSimilarityPercentage)) {
3081                $checkboxLabel.addClass(getCamouflagedClass(itemLuma[0], itemLuma[1], itemLuma[2]));
3082            }
3083        }
3084
3085        /**
3086         * Handles the color item checkbox changed event.
3087         * @param {ColorSelector} colorSelector The color selector.
3088         * @param {JQuery<HTMLInputElement>} $checkboxInput The checkbox input.
3089         */
3090        function onCheckboxCheckChanged(colorSelector, $checkboxInput) {
3091            styleInput($checkboxInput);
3092
3093            if (!colorSelector.allowMultiple) {
3094                // For single select (radio) bevahior, clicking a checkbox should always check it.
3095                // If the checkbox is already checked, then return false to prevent the default behavior of unchecking it.
3096
3097                // Uncheck all other checkboxes.
3098                var $colorSelectorItemContainers = colorSelector.$colorSelectorItemContainers;
3099
3100                for (var i = 0; i < $colorSelectorItemContainers.length; i++) {
3101                    var $otherItemInput = findInput($($colorSelectorItemContainers[i]));
3102
3103                    if ($otherItemInput[0] !== $checkboxInput[0]) {
3104                        $otherItemInput.prop("checked", false);
3105                        styleInput($otherItemInput);
3106                    }
3107                }
3108            }
3109        }
3110
3111        /**
3112         * 
3113         * @param {JQuery<HTMLInputElement>} $input
3114         * @param {JQuery<HTMLElement | undefined>} $label
3115         * @param {JQuery<HTMLElement | undefined} $container
3116         */
3117        function styleInput($input, $label, $container) {
3118            if (!$label) {
3119                $label = $input.parent("label");
3120            }
3121
3122            if (!$container) {
3123                $container = $input.closest(".color-selector-item-container");
3124            }
3125
3126            if ($input.prop("checked")) {
3127                $label.addClass("checked");
3128                $container.addClass("checked");
3129            }
3130            else {
3131                $label.removeClass("checked");
3132                $container.removeClass("checked");
3133            }
3134        }
3135
3136        /**
3137         * Gets the camouflaged CSS class for a luma color (rL, gL, bL).
3138         * @param {number} rLuma The R luma color.
3139         * @param {number} gLuma The G luma color.
3140         * @param {number} bLuma The B luma color.
3141         * @returns {string} The camouflaged CSS class for a luma color.
3142         */
3143        function getCamouflagedClass(rLuma, gLuma, bLuma) {
3144            var luma = rLuma + gLuma + bLuma;
3145            if (luma > 128) { // Max is 255
3146                return camouflagedLightClass;
3147            }
3148            else {
3149                return camouflagedDarkClass;
3150            }
3151        }
3152
3153        /**
3154         * Gets the first RGBA background color that isn't the default,
3155         * starting from the element and working its way up the
3156         * parent element chain.
3157         * @param {Element} el
3158         * @returns {Number[]} The first RGBA background color that isn't the default; otherwise, the default color is returned.
3159         */
3160        function getInheritedBackgroundColor(el) {
3161
3162            function getDefaultBackground() {
3163                var div = document.createElement("div");
3164                document.head.appendChild(div);
3165                var bg = window.getComputedStyle(div).backgroundColor;
3166                document.head.removeChild(div);
3167                return bg;
3168            }
3169
3170            // Get default style for current browser.
3171            var defaultStyle = getDefaultBackground(); // typically "rgba(0, 0, 0, 0)"
3172
3173            // Start with the supplied element.
3174            var elementsToProcess = [
3175                el
3176            ];
3177
3178            while (elementsToProcess.length) {
3179                var elementToProcess = elementsToProcess.shift();
3180
3181                if (!elementToProcess) {
3182                    // Skip to the next element if the current one is nullish.
3183                    continue;
3184                }
3185
3186                var backgroundColor = window.getComputedStyle(elementToProcess).backgroundColor;
3187
3188                // If we got a real value, return it.
3189                if (backgroundColor != defaultStyle) {
3190                    return ColorUtilities.rgbFuncToRgba(backgroundColor);
3191                }
3192                // Otherwise, process the next parent element.
3193                else if (elementToProcess.parentElement) {
3194                    elementsToProcess.push(elementToProcess.parentElement);
3195                }
3196            }
3197
3198            // If we have reached the top parent element without getting an explicit color,
3199            // return the default style.
3200            return ColorUtilities.rgbFuncToRgba(defaultStyle);
3201        }
3202
3203        // #endregion
3204
3205        // #endregion
3206
3207        // Keeps track of ColorSelector instances. 
3208        var colorSelectors = {};
3209
3210        var exports = {
3211            /**
3212             * Initializes a new ColorSelector instance.
3213             * @param {ColorSelectorInitOptions} options
3214             * @returns {ColorSelector}
3215             */
3216            initialize: function (options) {
3217                var cs = new ColorSelector(options);
3218                cs.initialize();
3219
3220                colorSelectors[options.controlId] = cs;
3221
3222                return cs;
3223            },
3224
3225            /**
3226             * Gets a ColorSelector instance by control ID.
3227             * @param {string} controlId The control ID.
3228             * @returns {ColorSelector}
3229             */
3230            get: function(controlId) {
3231                return colorSelectors[controlId];
3232            },
3233
3234            /**
3235             * Sets the camouflaged class if the element color is similar to the target element (or parent if not set).
3236             * @param {string} elementSelector The selector of the element to compare.
3237             * @param {string} targetSelector (Optional) The selector of the target element with which to compare colors. Defaults to the parent element.
3238             */
3239            setCamouflagedClass: function (elementSelector, targetSelector) {
3240                var element = document.querySelector(elementSelector);
3241                var target = targetSelector ? document.querySelector(target
3241Selector) : element.parentElement;
3242
3243                var elementColor = getInheritedBackgroundColor(element);
3244                var targetColor = getInheritedBackgroundColor(target);
3245
3246                var elementLuma = ColorUtilities.rgbToLuma(elementColor[0], elementColor[1], elementColor[2]);
3247                var targetLuma = ColorUtilities.rgbToLuma(targetColor[0], targetColor[1], targetColor[2]);
3248
3249                var isSamePerceivedBrightness = (
3250                    elementLuma[0] === targetLuma[0]
3251                    && elementLuma[1] === targetLuma[1]
3252                    && elementLuma[2] === targetLuma[2]
3253                );
3254
3255                // Short-circuit if elements have the same color.
3256                if (isSamePerceivedBrightness
3257                    || ColorUtilities.areColorsSimilar(elementLuma[0], elementLuma[1], elementLuma[2], targetLuma[0], targetLuma[1], targetLuma[2], colorSimilarityPercentage)) {
3258                    element.classList.add(getCamouflagedClass(elementLuma[0], elementLuma[1], elementLuma[2]));
3259                }
3260                else {
3261                    element.classList.remove(camouflagedLightClass);
3262                    element.classList.remove(camouflagedDarkClass);
3263                }
3264            }
3265        };
3266
3267        return exports;
3268    }());
3269}(jQuery));
3270;
3271(function ($)
3272{
3273  'use strict';
3274  window.Rock = window.Rock || {};
3275  Rock.controls = Rock.controls || {};
3276
3277  Rock.controls.datePartsPicker = (function ()
3278  {
3279    var exports = {
3280      initialize: function (options)
3281      {
3282        if (!options.id) {
3283          throw 'id is required';
3284        }
3285
3286      },
3287      clientValidate: function (validator, args)
3288      {
3289        var $datePartsPicker = $(validator).closest('.js-datepartspicker');
3290        var monthNumber = Number($datePartsPicker.find('.js-month').val());
3291        var dayNumber = Number($datePartsPicker.find('.js-day').val());
3292        var yearNumber = Number($datePartsPicker.find('.js-year').val());
3293        var required = $datePartsPicker.attr('data-required') == 'true';
3294        var requireYear = $datePartsPicker.attr('data-requireyear') == 'true';
3295        var allowFuture = $datePartsPicker.attr('data-allowFuture') == 'true';
3296        var itemLabelText = $datePartsPicker.attr('data-itemlabel');
3297
3298        var isValid = true;
3299
3300        if (!allowFuture) {
3301          if (monthNumber && dayNumber && yearNumber) {
3302            // NOTE: Javascript Date Contructor's month parameter is zero based!
3303            // see http://stackoverflow.com/questions/2552483/why-does-the-month-argument-range-from-0-to-11-in-javascripts-date-constructor
3304            var bDate = new Date(yearNumber, monthNumber-1, dayNumber);
3305            var now = new Date();
3306            if (bDate > now) {
3307              isValid = false;
3308              validator.errormessage = itemLabelText + ' cannot be a future date.'
3309            }
3310          }
3311        }
3312
3313        if (monthNumber && dayNumber && (yearNumber || !requireYear)) {
3314          // month, day and (conditionally) year are all set, it's OK
3315        }
3316        else if (monthNumber || dayNumber || yearNumber) {
3317          // at least one of them is set, but some are not, so it is invalid
3318          isValid = false;
3319          validator.errormessage = itemLabelText + ' must be a valid value.';
3320        }
3321        else if (required) {
3322          // nothing is set but it is a required field.
3323          isValid = false;
3324          validator.errormessage = itemLabelText + ' is required.';
3325        }
3326
3327        var control = $datePartsPicker
3328        if (isValid) {
3329          control.removeClass('has-error');
3330        } else {
3331          control.addClass('has-error');
3332        }
3333
3334        args.IsValid = isValid;
3335      }
3336    };
3337
3338    return exports;
3339  }());
3340}(jQuery));
3341;
3342(function ($) {
3343    'use strict';
3344    window.Rock = window.Rock || {};
3345    Rock.controls = Rock.controls || {};
3346
3347    Rock.controls.datePicker = (function () {
3348        var exports = {
3349            initialize: function (options) {
3350                if (!options.id) {
3351                    throw 'id is required';
3352                }
3353                var dateFormat = 'mm/dd/yyyy';
3354                if (options.format) {
3355                    dateFormat = options.format;
3356                }
3357
3358                var $textBox = $('#' + options.id);
3359
3360                var $datePickerContainer = $textBox.closest('.js-date-picker-container');
3361                var $datePickerInputGroup = $textBox.closest('.input-group.js-date-picker');
3362
3363                // uses https://github.com/uxsolutions/bootstrap-datepicker
3364                var datePicker = $datePickerInputGroup.datepicker({
3365                    format: dateFormat,
3366                    assumeNearbyYear: 10,
3367                    autoclose: true,
3368                    container: options.container || document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || "body",
3369                    orientation: options.orientation || "auto",
3370                    todayBtn: "linked",
3371                    forceParse: options.forceParse,
3372                    startDate: options.startDate,
3373                    endDate: options.endDate || new Date(8640000000000000),
3374                    startView: options.startView,
3375                    showOnFocus: options.showOnFocus,
3376                    todayHighlight: options.todayHighlight,
3377                    zIndexOffset: 1050
3378                });
3379
3380                // Listen to actions from external code.
3381                if (options.actions) {
3382                    // Update the bootstrap datepicker when external code sets the date.
3383                    options.actions.onSetLocalDate = (localDate) => {
3384                        $datePickerInputGroup.data("datepicker")
3385                            .setDate(localDate) // Set the date.
3386                            .fill();            // Update the UI.
3387                    };
3388                }
3389
3390                // note: using 'change' instead of datePicker's 'changeDate' so that both manual entry and picking from calender works
3391                datePicker.on('change', function (e) {
3392                    if (options.postbackScript) {
3393                        window.location = "javascript:" + options.postbackScript;
3394                    }
3395
3396                    if (options.onChangeScript) {
3397                        options.onChangeScript();
3398                    }
3399                });
3400
3401                // if the guest clicks the addon select all the text in the input
3402                $datePickerInputGroup.find('.input-group-addon').on('click', function () {
3403                    $(this).siblings('.form-control').select();
3404                });
3405
3406                $datePickerContainer.find('.js-current-date-checkbox').on('click', function (a, b, c) {
3407                    var $dateOffsetBox = $datePickerContainer.find('.js-current-date-offset');
3408                    var $dateOffsetlabel = $("label[for='" + $dateOffsetBox.attr('id') + "']")
3409                    if ($(this).is(':checked')) {
3410                        $dateOffsetlabel.show();
3411                        $dateOffsetBox.show();
3412
3413                        // set textbox val to something instead of empty string so that validation doesn't complain
3414                        $textBox.data("last-value", $textBox.val()).val('Current').prop('disabled', true).addClass('aspNetDisabled');
3415
3416                    } else {
3417                        $dateOffsetlabel.hide();
3418                        $dateOffsetBox.hide();
3419
3420                        // set textbox val to last value so that validation will work again (if it is enabled)
3421                        $textBox.val($textBox.data('last-value')).prop('disabled', false).removeClass('aspNetDisabled');
3422                    }
3423                });
3424            }
3425        };
3426
3427        return exports;
3428    }());
3429}(jQuery));
3430;
3431(function ($)
3432{
3433  'use strict';
3434  window.Rock = window.Rock || {};
3435  Rock.controls = Rock.controls || {};
3436
3437  Rock.controls.dateRangePicker = (function ()
3438  {
3439    var exports = {
3440      initialize: function (options)
3441      {
3442        if (!options.id) {
3443          throw 'id is required';
3444        }
3445      },
3446      clientValidate: function (validator, args)
3447      {
3448        var $dateRangePicker = $(validator).closest('.js-daterangepicker');
3449        var lowerValue = $dateRangePicker.find('.js-lower input').val();
3450        var upperValue = $dateRangePicker.find('.js-upper input').val();
3451        var required = $dateRangePicker.attr('data-required') == 'true';
3452        var itemLabelText = $dateRangePicker.attr('data-itemlabel');
3453
3454        var isValid = true;
3455
3456        if (required) {
3457            // if required, then make sure that the date range has a start and/or end date (can't both be blank)
3458            if (lowerValue.length == 0 && upperValue.length == 0) {
3459                isValid = false;
3460                validator.errormessage = itemLabelText + " is required";
3461            }
3462        }
3463
3464        var control = $dateRangePicker;
3465        if (isValid)
3466        {
3467          control.removeClass('has-error');
3468        }
3469        else
3470        {
3471          control.addClass('has-error');
3472        }
3473
3474        args.IsValid = isValid;
3475      }
3476    };
3477
3478    return exports;
3479  }());
3480}(jQuery));
3481;
3482(function ($) {
3483    'use strict';
3484    window.Rock = window.Rock || {};
3485    Rock.controls = Rock.controls || {};
3486
3487    Rock.controls.dateTimePicker = (function () {
3488        var exports = {
3489            initialize: function (options) {
3490                if (!options.id) {
3491                    throw 'id is required';
3492                }
3493                var dateFormat = 'mm/dd/yyyy';
3494                if (options.format) {
3495                    dateFormat = options.format;
3496                }
3497
3498                var $dp = $('#' + options.id + " .js-datetime-date");
3499
3500                var $dateTimePickerContainer = $dp.closest('.js-datetime-picker-container');
3501                var $dateTimePickerInputGroup = $dp.closest('.input-group.date');
3502
3503                // uses https://github.com/uxsolutions/bootstrap-datepicker
3504                $dateTimePickerInputGroup.datepicker({
3505                    format: dateFormat,
3506                    assumeNearbyYear: 10,
3507                    autoclose: true,
3508                    todayBtn: "linked",
3509                    startView: options.startView || 'month',
3510                    todayHighlight: options.todayHighlight || true,
3511                    zIndexOffset: 1050
3512                });
3513
3514                // if the guest clicks the addon select all the text in the input
3515                $dateTimePickerInputGroup.find('.input-group-addon').on('click', function ()
3516                {
3517                  $(this).siblings('.form-control').select();
3518                });
3519
3520                var $tp = $('#' + options.id + " .js-datetime-time");
3521                if ($tp) {
3522                    var $tpid = $tp.attr('id');
3523                    if ($tpid) {
3524                        Rock.controls.timePicker.initialize({
3525                            id: $tpid
3526                        });
3527                    }
3528                }
3529
3530                $dateTimePickerContainer.find('.js-current-datetime-checkbox').on('click', function (a, b, c) {
3531                    var $dateTimeOffsetBox = $dateTimePickerContainer.find('.js-current-datetime-offset');
3532                    var $dateOffsetlabel = $("label[for='" + $dateTimeOffsetBox.attr('id') + "']")
3533                    if ($(this).is(':checked')) {
3534                        $dateOffsetlabel.removeClass('aspNetDisabled').show();
3535                        $dateTimeOffsetBox.show();
3536                        $dateTimeOffsetBox.prop('disabled', false).removeClass('aspNetDisabled').val( $dateTimeOffsetBox.data('last-value') );
3537                        $dp.data( "last-value", $dp.val()).val('').prop('disabled', true).addClass('aspNetDisabled').prop('placeholder', 'Current');
3538                        $tp.data( "last-value", $tp.val()).val('').prop('disabled', true).addClass('aspNetDisabled');
3539                    } else {
3540                        $dateOffsetlabel.addClass('aspNetDisabled').hide();
3541                        $dateTimeOffsetBox.data( "last-value", $dateTimeOffsetBox.val()).hide();
3542                        $dateTimeOffsetBox.val('').prop('disabled', true).addClass('aspNetDisabled');
3543                        $dp.prop('disabled', false).removeClass('aspNetDisabled').prop('placeholder', '').val($dp.data('last-value'));
3544                        $tp.prop('disabled', false).removeClass('aspNetDisabled').val($tp.data('last-value'));
3545                    }
3546                });
3547            }
3548        };
3549
3550        return exports;
3551    }());
3552}(jQuery));
3553;
3554(function ($) {
3555    'use strict';
3556    window.Rock = window.Rock || {};
3557    Rock.controls = Rock.controls || {};
3558
3559    Rock.controls.electronicSignatureControl = (function () {
3560        var exports = {
3561            /**
3562             * 
3563             * @param {any} options
3564             */
3565            initialize: function (options) {
3566                if (!options.controlId) {
3567                    throw 'id is required';
3568                }
3569
3570                var self = this;
3571
3572                self.ImageMimeType = options.imageMimeType || "image/png";
3573
3574                var $control = $('#' + options.controlId);
3575
3576                if ($control.length == 0) {
3577                    return;
3578                }
3579
3580                self.$signatureControl = $control;
3581                self.$signatureTyped = $('.js-signature-typed', $control);
3582
3583                self.$signatureEntryDrawn = $('.js-signature-entry-drawn', $control);
3584                self.$signaturePadCanvas = $('.js-signature-pad-canvas', $control);
3585                self.$signatureImageDataURL = $('.js-signature-data', $control);
3586                
3587                self.$clearSignature = $('.js-clear-signature', $control);
3588                self.$saveSignature = $('.js-save-signature', $control);
3589
3590                self.initializeSignaturePad();
3591
3592                self.$saveSignature.click(function () {
3593                    if (!self.signatureIsValid(self.$signatureControl)) {
3594                        return false;
3595                    }
3596
3597                    if (self.$signaturePadCanvas.length) {
3598                        var signaturePad = self.$signaturePadCanvas.data('signatureComponent');
3599
3600                        if (signaturePad) {
3601                            var signatureImageDataUrl = signaturePad.toDataURL(self.ImageMimeType);
3602                            self.$signatureImageDataURL.val(signatureImageDataUrl);
3603                        }
3604                    }
3605                });
3606            },
3607
3608            /** */
3609            initializeSignaturePad: function () {
3610                var self = this;
3611                if (!self.$signaturePadCanvas.length) {
3612                    return
3613                }
3614
3615                var signaturePadOptions = {};
3616                if (self.ImageMimeType == "image/jpeg") {
3617                    // NOTE That if we use jpeg, we'll have to deal with this https://github.com/szimek/signature_pad/issues/584
3618                    signaturePadOptions = {
3619                        penColor: 'black',
3620                        backgroundColor: 'white'
3621                    }
3622                };
3623
3624                var signaturePad = new SignaturePad(self.$signaturePadCanvas[ 0 ], signaturePadOptions);
3625
3626                self.$signaturePadCanvas.data('signatureComponent', signaturePad);
3627
3628                self.$clearSignature.click(function () {
3629                    signaturePad.clear();
3630                })
3631
3632                window.addEventListener("resize", function () {
3633                    self.resizeSignatureCanvas(self)
3634                });
3635
3636                self.resizeSignatureCanvas(self);
3637            },
3638
3639            /** */
3640            resizeSignatureCanvas: function (signatureControl) {
3641                if (!signatureControl.$signaturePadCanvas.length) {
3642                    return;
3643                }
3644
3645                // If the window is resized, that'll affect the drawing canvas
3646                // also, if there is an existing signature, it'll get messed up, so clear it and
3647                // make them sign it again. See additional details why 
3648                // https://github.com/szimek/signature_pad
3649                var signaturePadCanvas = signatureControl.$signaturePadCanvas[ 0 ];
3650                var containerWidth = signatureControl.$signatureEntryDrawn.width();
3651                if (!containerWidth || containerWidth == 0) {
3652                    containerWidth = 400;
3653                }
3654
3655                // Note the suggestion  https://github.com/szimek/signature_pad#handling-high-dpi-screens
3656                // to re-calculate the ratio based on window.devicePixelRatio isn't needed. 
3657                // We can just use the width() of the container and use fixed height of 100.
3658                const ratio = 1;
3659                signaturePadCanvas.width = containerWidth * ratio;
3660                signaturePadCanvas.height = 100 * ratio;
3661                signaturePadCanvas.getContext("2d").scale(ratio, ratio);
3662
3663                var signaturePad = signatureControl.$signaturePadCanvas.data('signatureComponent');
3664                signaturePad.clear();
3665            },
3666
3667            signatureIsValid: function ($signatureControl) {
3668                var $signatureCanvas = $signatureControl.find('.js-signature-pad-canvas')
3669                var $signatureTyped = $signatureControl.find('.js-signature-typed');
3670
3671                if ($signatureCanvas.length == 0 && $signatureTyped.length == 0) {
3672                    return;
3673                }
3674
3675                var isValid = true;
3676
3677                if ($signatureCanvas.length) {
3678                    var signatureCanvas = $signatureCanvas.data('signatureComponent');
3679
3680                    if (signatureCanvas.isEmpty()) {
3681                        isValid = false;
3682                    }
3683                }
3684                else {
3685                    if ($signatureTyped.val().trim() == '') {
3686                        isValid = false;
3687                    }
3688                }
3689
3690                return isValid;
3691            },
3692
3693            /** */
3694            clientValidate: function (validator, args) {
3695                var $signatureControl = $(validator).closest('.js-electronic-signature-control');
3696
3697                var isValid = this.signatureIsValid($signatureControl);
3698                
3699                if (!isValid) {
3700                    validator.errormessage = "Signature is required.";
3701                }
3702
3703                args.IsValid = isValid;
3704            }
3705        };
3706
3707        return exports;
3708    }());
3709}(jQuery));
3710;
3711(function (Sys) {
3712    'use strict';
3713    Sys.Application.add_load(function () {
3714
3715        var preventDoublePostback = function () {
3716            var inputOnChange = $(this).attr('onchange');
3717            var inputOnClick = $(this).attr('onclick');
3718            if (inputOnChange && inputOnChange.indexOf('__doPostBack') > 0 || inputOnClick && inputOnClick.indexOf('__doPostBack') > 0) {
3719                var $blockButtons = $(this).closest('.js-block-instance').find('a');
3720                $blockButtons.attr('disabled', 'disabled')
3721                    .prop('disabled', true)
3722                    .attr('disabled', 'disabled')
3723                    .addClass('disabled');
3724            }
3725        };
3726
3727        // since some of the edit controls do a postback to show/hide other controls, we want to disable buttons to prevent a double postback
3728        $('select.js-prevent-double-postback,input.js-prevent-double-postback').on('change', preventDoublePostback);
3729        $('.js-prevent-double-postback input[type="radio"]').on('click', preventDoublePostback);
3730    });
3731}(Sys));
3732;
3733(function ($) {
3734    'use strict';
3735    window.Rock = window.Rock || {};
3736    Rock.controls = Rock.controls || {};
3737
3738    Rock.controls.fileUploader = (function () {
3739        var _configure = function (options) {
3740            options.isBinaryFile = options.isBinaryFile || 'T';
3741            options.uploadUrl = options.uploadUrl || 'FileUploader.ashx';
3742
3743            var wsUrl = Rock.settings.get('baseUrl')
3744                        + options.uploadUrl + '?'
3745                        + 'isBinaryFile=' + options.isBinaryFile;
3746
3747            if (options.isBinaryFile === 'T') {
3748                wsUrl += '&fileId=' + options.fileId
3749                    + '&fileTypeGuid=' + options.fileTypeGuid;
3750            }
3751            else {
3752                // note rootFolder is encrypted to prevent direct access to filesystem via the URL
3753                wsUrl += '&rootFolder=' + (encodeURIComponent(options.rootFolder) || '');
3754            }
3755
3756            if (options.isTemporary === 'F') {
3757              wsUrl += '&IsTemporary=False';
3758            }
3759
3760            if (options.parentEntityTypeId !== 'null') {
3761                wsUrl += '&ParentEntityTypeId=' + options.parentEntityTypeId;
3762            }
3763
3764            if (options.parentEntityId !== 'null') {
3765                wsUrl += '&ParentEntityId=' + options.parentEntityId;
3766            }
3767
3768            // uses https://github.com/blueimp/jQuery-File-Upload
3769            $('#' + options.controlId).fileupload({
3770                url: wsUrl,
3771                dataType: 'json',
3772                dropZone: $('#' + options.controlId).closest('.fileupload-dropzone'),
3773                autoUpload: true,
3774                submit: options.submitFunction,
3775                sequentialUploads: true,
3776                start: function (e, data) {
3777                    var $el = $('#' + options.controlId).closest('.fileupload-group');
3778                    $el.find('.js-upload-progress').rockFadeIn();
3779                },
3780                progressall: function (e, data) {
3781                    try {
3782                        if (data.total > 0) {
3783                            var $el = $('#' + options.controlId).closest('.fileupload-group');
3784                            var $progressPercent = $el.find('.js-upload-progress-percent');
3785                            if (!$progressPercent.length) {
3786                                return;
3787                            }
3788
3789                            var percent = (data.loaded * 100 / data.total).toFixed(0);
3790                            if (percent > 1 && percent < 99) {
3791                                $progressPercent.text(percent + "%"); 
3792                            }
3793                            else {
3794                                $progressPercent.text("");
3795                            }
3796                        }
3797                    }
3798                    catch (ex) {
3799                        // ignore if any exception occurs
3800                    }
3801                },
3802                stop: function (e) {
3803                    var $el = $('#' + options.controlId).closest('.fileupload-group');
3804                    $el.find('.js-upload-progress').hide();
3805                    $el.find('.fileupload-dropzone').show();
3806                },
3807                done: function (e, data) {
3808                    var $el = $('#' + options.aFileName);
3809
3810                    if ((options.isBinaryFile || 'T') === 'F') {
3811                        $('#' + options.hfFileId).val(data.response().result.FileName);
3812                    }
3813                    else {
3814                        $('#' + options.hfFileId).val(data.response().result.Id);
3815                    }
3816
3817                    var getFileUrl = Rock.settings.get('baseUrl')
3818                        + 'GetFile.ashx?'
3819                        + 'isBinaryFile=' + (options.isBinaryFile || 'T')
3820                        // note rootFolder is encrypted to prevent direct access to filesystem via the URL
3821                        + '&rootFolder=' + (encodeURIComponent(options.rootFolder) || '');
3822
3823                    if (options.disablePredictableIds) {
3824                        getFileUrl += '&guid=' + data.response().result.Guid;
3825                    }
3826                    else {
3827                        getFileUrl += '&id=' + data.response().result.Id
3828                    }
3829
3830                    getFileUrl += '&fileName=' + data.response().result.FileName;
3831
3832                    $el.text(data.response().result.FileName).attr('href', getFileUrl);
3833                    $('#' + options.aRemove).show();
3834                    if (options.postbackScript) {
3835                        window.location = "javascript:" +  options.postbackScript;
3836                    }
3837
3838                    if (options.doneFunction) {
3839                        options.doneFunction(e, data);
3840                    }
3841                },
3842                fail: function (e, data) {
3843                    var $el = $('#' + options.controlId).closest('.fileupload-group');
3844                    $el.siblings('.js-rockupload-alert').remove();
3845                    var $warning = $('<div class="alert alert-warning alert-dismissable js-rockupload-alert"/>');
3846
3847                    var msg = "unable to upload";
3848                    if (data.response().jqXHR && data.response().jqXHR.status == 406) {
3849                        msg = "file type not allowed";
3850                    } else if (data.response().jqXHR && data.response().jqXHR.responseText) {
3851                        msg = data.response().jqXHR.responseText;
3852                    }
3853
3854                    if (options.maxUploadBytes && data.total) {
3855                        if (data.total >= options.maxUploadBytes) {
3856                            msg = "file size is limited to " + (options.maxUploadBytes / 1024 / 1024) + "MB";
3857                        }
3858                    }
3859
3860                    $warning.append('<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>')
3861                        .append('<strong><i class="ti ti-alert-triangle"></i> Warning </strong>')
3862                        .append(msg);
3863                    $warning.insertBefore($el);
3864                }
3865            });
3866
3867            $('#' + options.aRemove).on('click', function () {
3868                $(this).hide();
3869                var $el = $('#' + options.aFileName);
3870                $el.attr('href', '#');
3871                $el.text('');
3872                $el.removeClass('file-exists');
3873
3874                if (options.postbackRemovedScript) {
3875                    window.location = "javascript:" + options.postbackRemovedScript;
3876                } else {
3877                    $('#' + options.hfFileId).val('0');
3878                }
3879
3880                return false;
3881            });
3882        },
3883        exports = {
3884            initialize: function (options) {
3885                if (!options.controlId) throw 'Control ID must be set.';
3886                _configure(options);
3887            }
3888        };
3889
3890        return exports;
3891    }());
3892}(jQuery));
3893;
3894(function ($) {
3895    'use strict';
3896    window.Rock = window.Rock || {};
3897    Rock.controls = Rock.controls || {};
3898
3899    Rock.controls.firstNameTextBox = (function () {
3900        const exports = {
3901            initialize: function (options) {
3902                if (!options.id) {
3903                    throw new Error('id is required');
3904                }
3905                this.options = options;
3906            },
3907            clientValidate: function (validator, args) {
3908                const firstNameTextBox = document.getElementById(this.options.id);
3909                const notAllowedStrings = this.options.notAllowedStrings;
3910                let firstName = undefined;
3911                if (firstNameTextBox) {
3912                    firstName = firstNameTextBox.value.toUpperCase();
3913                }
3914
3915                let isValid = true;
3916                let invalidString;
3917
3918                if (firstName) {
3919                    for (const value of notAllowedStrings) {
3920                        if (firstName.includes(value.toUpperCase())) {
3921                            isValid = false;
3922                            invalidString = value;
3923                            break;
3924                        }
3925                    }
3926                }
3927                if (!isValid) {
3928                    const labelText = firstNameTextBox.dataset.itemLabel;
3929                    validator.errormessage = `${labelText} cannot contain: ${invalidString}`;
3930
3931                    if (this.options.displayInlineValidationError) {
3932                        validator.innerHTML = validator.errormessage;
3933                    }
3934                }
3935
3936                args.IsValid = isValid;
3937            }
3938        };
3939
3940        return exports;
3941    }());
3942}(jQuery));
3943;
3944(function ($) {
3945    'use strict';
3946    window.Rock = window.Rock || {};
3947    Rock.controls = Rock.controls || {};
3948
3949    Rock.controls.followingsToggler = (function () {
3950
3951        var exports = {
3952            // initialize a script that will set the following status via REST
3953            // entityTypeId is the EntityType, and entityId is the .Id for the associated entity
3954            // personId and personAliasId are the person that is doing the following/un-following
3955            // callback is optional and called on follow or unfollow events with a param indicating
3956            // if the person is now following
3957            initialize: function ($followingDiv, entityTypeId, entityId, purposeKey, personId, personAliasId, callback) {
3958                var hasCallback = typeof callback === 'function';
3959
3960                $followingDiv.on('click', function () {
3961                    if ($followingDiv.hasClass('following')) {
3962
3963                        $.ajax({
3964                            type: 'DELETE',
3965                            url: Rock.settings.get('baseUrl') + 'api/followings/' + entityTypeId + '/' + entityId + '/' + personId + '?purposeKey=' + encodeURIComponent(purposeKey),
3966                            success: function (data, status, xhr) {
3967                                $followingDiv.removeClass('following');
3968
3969                                // update the tooltip (if one was configured)
3970                                if ($followingDiv.attr('data-original-title')) {
3971                                    $followingDiv.attr('data-original-title', 'Click to follow');
3972                                }
3973
3974                                if (hasCallback) {
3975                                    callback(false, $followingDiv, entityTypeId, entityId, personId, personAliasId);
3976                                }
3977                            },
3978                        });
3979
3980                    } else {
3981                        var following = {
3982                            EntityTypeId: entityTypeId,
3983                            EntityId: entityId,
3984                            PersonAliasId: personAliasId,
3985                            PurposeKey: purposeKey
3986                        };
3987
3988                        $.ajax({
3989                            type: 'POST',
3990                            contentType: 'application/json',
3991                            data: JSON.stringify(following),
3992                            url: Rock.settings.get('baseUrl') + 'api/followings',
3993                            statusCode: {
3994                                201: function () {
3995                                    $followingDiv.addClass('following');
3996
3997                                    // update the tooltip (if one was configured)
3998                                    if ($followingDiv.attr('data-original-title')) {
3999                                        $followingDiv.attr('data-original-title', 'Currently following');
4000                                    }
4001
4002                                    if (hasCallback) {
4003                                        callback(true, $followingDiv, entityTypeId, entityId, personId, personAliasId);
4004                                    }
4005                                }
4006                            }
4007                        });
4008                    }
4009                })
4010            }
4011        };
4012
4013        return exports;
4014    }());
4015}(jQuery));
4016;
4017(function ($) {
4018    'use strict';
4019    window.Rock = window.Rock || {};
4020    Rock.controls = Rock.controls || {};
4021
4022    Rock.controls.fullScreen = (function () {
4023        var exports = {
4024            initialize: function (el) {
4025                $('.js-fullscreen-trigger').on('click', function (e) {
4026                    e.preventDefault();
4027                    var elem = $(this).closest('.block-instance')[0] || document.documentElement
4028                    if (el) {
4029                        // If Requesting Fullscreen page, verify layout is fullscreen capable.
4030                        if (el === 'body' && $(this).parents('.page-fullscreen-capable').length < 1) {
4031                            elem = $(this).closest('.block-instance')[0]
4032                        } else {
4033                            elem = $(el)[0];
4034                        }
4035                    }
4036                    Rock.controls.fullScreen.toggleFullscreen(elem);
4037                });
4038            },
4039            toggleFullscreen: function (elem) {
4040                elem = elem || document.documentElement;
4041                if (!document.fullscreenElement && !document.mozFullScreenElement &&
4042                    !document.webkitFullscreenElement && !document.msFullscreenElement) {
4043                    if (elem.requestFullscreen) {
4044                    elem.requestFullscreen();
4045                    document.addEventListener('fullscreenchange', this.exitHandler, false);
4046                    } else if (elem.msRequestFullscreen) {
4047                    elem.msRequestFullscreen();
4048                    document.addEventListener('MSFullscreenChange', this.exitHandler, false);
4049                    } else if (elem.mozRequestFullScreen) {
4050                    elem.mozRequestFullScreen();
4051                    document.addEventListener('mozfullscreenchange', this.exitHandler, false);
4052                    } else if (elem.webkitRequestFullscreen) {
4053                    elem.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
4054                    document.addEventListener('webkitfullscreenchange', this.exitHandler, false);
4055                    }
4056                    $(elem).addClass('is-fullscreen');
4057                } else {
4058                    if (document.exitFullscreen) {
4059                    document.exitFullscreen();
4060                    } else if (document.msExitFullscreen) {
4061                    document.msExitFullscreen();
4062                    } else if (document.mozCancelFullScreen) {
4063                    document.mozCancelFullScreen();
4064                    } else if (document.webkitExitFullscreen) {
4065                    document.webkitExitFullscreen();
4066                    }
4067                    $(elem).removeClass('is-fullscreen');
4068                }
4069            },
4070            exitHandler: function (elem) {
4071                if (!document.webkitIsFullScreen && !document.mozFullScreen && !document.msFullscreenElement) {
4072                    $(elem.target).removeClass('is-fullscreen');
4073                    document.dispatchEvent(new Event('RockExitFullscreen'));
4074                }
4075            }
4076        };
4077
4078        return exports;
4079    }());
4080}(jQuery));
4081;
4082(function ($) {
4083    'use strict';
4084    window.Rock = window.Rock || {};
4085    Rock.controls = Rock.controls || {};
4086
4087    Rock.controls.geoPicker = (function () {
4088
4089        var GeoPicker = function (options) {
4090            var obj = this;
4091            obj.controlId = options.controlId;
4092            obj.restUrl = options.restUrl;
4093            obj.path = options.path;
4094            obj.centerAddress = options.centerAddress;                      // used when nothing is on map
4095            obj.centerLatitude = options.centerLatitude || "33.590795";     // used when nothing is on map
4096            obj.centerLongitude = options.centerLongitude || "-112.126459"; // used when nothing is on map
4097            obj.drawingMode = options.drawingMode || "Polygon" || "Point";  // the available modes
4098            obj.strokeColor = options.strokeColor || "#0088cc";
4099            obj.fillColor = options.fillColor || "#0088cc";
4100            obj.mapId = options.mapId;
4101
4102            // An array of styles that controls the look of the Google Map
4103            // http://gmaps-samples-v3.googlecode.com/svn/trunk/styledmaps/wizard/index.html
4104            obj.styles = options.mapStyle;
4105
4106            // This is used to temporarily store in case of user cancel.
4107            obj.pathTemp = null;
4108
4109            // the selected polygon or point
4110            obj.selectedShape = null;
4111
4112            // these are used to set the map's viewport boundary
4113            obj.minLat = null;
4114            obj.maxLat = null;
4115            obj.minLng = null;
4116            obj.maxLng = null;
4117
4118            // our google Map
4119            obj.map = null;
4120
4121            // an instance of the Google Maps drawing manager
4122            obj.drawingManager = null;
4123
4124            // An array of styles that controls the look of the Google Map
4125            // http://gmaps-samples-v3.googlecode.com/svn/trunk/styledmaps/wizard/index.html
4126            //obj.styles = [
4127            //  {
4128            //      "stylers": [
4129            //        { "visibility": "simplified" },
4130            //        { "saturation": -100 }
4131            //      ]
4132            //  }, {
4133            //      "featureType": "road",
4134            //      "elementType": "labels",
4135            //      "stylers": [
4136            //        { "visibility": "on" }
4137            //      ]
4138            //  }, {
4139            //      "featureType": "poi",
4140            //      "stylers": [
4141            //        { "visibility": "off" }
4142            //      ]
4143            //  }, {
4144            //      "featureType": "landscape",
4145            //      "stylers": [
4146            //        { "visibility": "off" }
4147            //      ]
4148            //  }, {
4149            //      "featureType": "administrative.province",
4150            //      "stylers": [
4151            //        { "visibility": "on" }
4152            //      ]
4153            //  }, {
4154            //      "featureType": "administrative.locality",
4155            //      "stylers": [
4156            //        { "visibility": "on" }
4157            //      ]
4158            //  }
4159            //];
4160
4161            /**
4162            * Initializes the map viewport boundary coordinates.
4163            */
4164            this.initMinMaxLatLng = function () {
4165                obj.minLat = null;
4166                obj.maxLat = null;
4167                obj.minLng = null;
4168                obj.maxLng = null;
4169            }
4170
4171            /**
4172            * Unselects the selected shape (if selected) and disables the delete button.
4173            */
4174            this.clearSelection = function () {
4175                if (obj.selectedShape) {
4176                    if (obj.drawingMode == "Polygon") {
4177                        obj.selectedShape.setEditable(false);
4178                    }
4179                    obj.selectedShape = null;
4180                }
4181                $('#gmnoprint-delete-button_' + obj.controlId).attr('disabled', '')
4182                    .find('.ti-x').css("color", "#aaa");
4183            }
4184
4185            /**
4186            * Stores the given shape's path points into the path property as a
4187            * pipe (|) delimited list of lat,long coordinates.  This method also
4188            * re-enables the delete button so that the polygon can be deleted.
4189            */
4190            this.setSelection = function (shape, type) {
4191                obj.clearSelection();
4192
4193                // enable delete button
4194                $('#gmnoprint-delete-button_' + obj.controlId).prop("disabled", false)
4195                    .find('.ti-x').css("color", "");
4196
4197                obj.selectedShape = shape;
4198
4199                if (type == "polygon") {
4200                    shape.setEditable(true);
4201                    var coordinates = new Array();
4202                    var vertices = shape.getPaths().getAt(0);
4203                    // Iterate over the vertices of the shape's path
4204                    for (var i = 0; i < vertices.length; i++) {
4205                        var xy = vertices.getAt(i);
4206                        coordinates[i] = xy.toUrlValue();
4207                    }
4208
4209                    // if the last vertex is not already the first, then
4210                    // add the first vertex to the end of the path.
4211                    if (vertices.getAt(0).toUrlValue() != coordinates[coordinates.length - 1]) {
4212                        obj.path = coordinates.join('|') + '|' + vertices.getAt(0).toUrlValue();
4213                    }
4214                    else {
4215                        obj.path = coordinates.join('|');
4216                    }
4217                }
4218                else if (type == "marker") {
4219                    obj.path = shape.getPosition().toUrlValue();
4220                }
4221            }
4222
4223            /**
4224            * Delete the selected shape and enable the drawing controls
4225            * if they were deleted.  Also removes the polygon from the hidden variable.
4226            */
4227            this.deleteSelectedShape = function () {
4228                if (obj.selectedShape && confirm("Delete selected shape?")) {
4229                    obj.selectedShape.setMap(null);
4230                    obj.clearSelection();
4231
4232                    // delete the path
4233                    obj.path = null;
4234
4235                    // enable the drawing controls again
4236                    obj.drawingManager.setOptions({
4237                        drawingControlOptions: {
4238                            drawingModes: obj.getDrawingModes()
4239                        }
4240                    });
4241                }
4242            }
4243
4244            /**
4245            * Returns the appropriate mode array for use with the map's DrawingManager
4246            * drawing control options.
4247            */
4248            this.getDrawingModes = function getDrawingModes() {
4249
4250                if (obj.drawingMode == "Polygon") {
4251                    return [google.maps.drawing.OverlayType.POLYGON];
4252                }
4253                else if (obj.drawingMode == "Point") {
4254                    return [google.maps.drawing.OverlayType.MARKER];
4255                }
4256            }
4257
4258            /**
4259            * Returns a marker image styled according to the stroke color.
4260            */
4261            this.getMarkerImage = function getMarkerImage() {
4262                return {
4263                    path: 'M 0,0 C -2,-20 -10,-22 -10,-30 A 10,10 0 1,1 10,-30 C 10,-22 2,-20 0,0 z',
4264                    fillColor: '#FE7569',
4265                    fillOpacity: 1,
4266                    strokeColor: '#000',
4267                    strokeWeight: 1,
4268                    scale: 1,
4269                    labelOrigin: new google.maps.Point(0, -28)
4270                }
4271            }
4272
4273            /**
4274            * Finds the point/polygon boundary and sets the map viewport to fit
4275            */
4276            this.fitBounds = function () {
4277                if (! obj.path) {
4278                    // if no path, then set the center using the options
4279                    var newLatLng = new google.maps.LatLng(
4280                        parseFloat(obj.centerLatitude),
4281                        parseFloat(obj.centerLongitude));
4282                    obj.map.setCenter(newLatLng);
4283                    return;
4284                }
4285
4286                var coords = obj.path.split('|');
4287                var pathArray = new Array();
4288                // find the most southWest and northEast points of the path.
4289                for (var i = 0; i < coords.length ; i++) {
4290                    var latLng = coords[i].split(',');
4291                    var lat = parseFloat(latLng[0]);
4292                    var lng = parseFloat(latLng[1]);
4293                    // find the most southWest and northEast points of the path.
4294                    obj.findBounds(lat, lng);
4295                    pathArray.push(new google.maps.LatLng(lat, lng));
4296                }
4297
4298                // Set the viewport to contain the given bounds.
4299                var southWest = new google.maps.LatLng(obj.minLat, obj.minLng);
4300                var northEast = new google.maps.LatLng(obj.maxLat, obj.maxLng);
4301                var bounds = new google.maps.LatLngBounds(southWest, northEast);
4302                obj.map.fitBounds(bounds);
4303
4304                if (obj.drawingMode == "Point") {
4305                    obj.map.setZoom(16);
4306                }
4307            }
4308
4309            /**
4310            * Utility method to determine the most SouthWestern
4311            * and NorthEastern lat and long.
4312            */
4313            this.findBounds = function (lat, lng) {
4314                if (!obj.minLat || lat < obj.minLat) {
4315                    obj.minLat = lat;
4316                }
4317                if (!obj.maxLat || lat > obj.maxLat) {
4318                    obj.maxLat = lat;
4319                }
4320                if (!obj.minLng || lng < obj.minLng) {
4321                    obj.minLng = lng;
4322                }
4323                if (!obj.maxLng || lng > obj.maxLng) {
4324                    obj.maxLng = lng;
4325                }
4326            }
4327
4328            /**
4329            * Disables the drawing manager so they cannot add anything to the map.
4330            */
4331            this.disableDrawingManager = function () {
4332                // Switch back to non-drawing mode after drawing a shape.
4333                if (!obj.drawingManager) {
4334                    return;
4335                }
4336
4337                obj.drawingManager.setDrawingMode(null);
4338
4339                // disable the drawing controls so we only get one polygon
4340                // and we'll add it back on deleting the existing polygon.
4341                obj.drawingManager.setOptions({
4342                    drawingControlOptions: {
4343                        drawingModes: [
4344                        ]
4345                    }
4346                });
4347            }
4348
4349            /**
4350            * Takes the path data stored in the path and plots it on the map.
4351            */
4352            this.plotPath = function (map) {
4353                obj.initMinMaxLatLng();
4354                // only try this if we have a path
4355                if (obj.path) {
4356                    var coords = obj.path.split('|');
4357                    var pathArray = new Array();
4358                    // put the polygon coordinates into a path array
4359                    for (var i = 0; i < coords.length ; i++) {
4360                        var latLng = coords[i].split(',');
4361                        var lat = parseFloat(latLng[0]);
4362                        var lng = parseFloat(latLng[1]);
4363                        pathArray.push(new google.maps.LatLng(lat, lng));
4364                    }
4365
4366                    // reverse geocode the first point to an address and display.
4367                    var $label = $('#selectedGeographyLabel_' + this.controlId);
4368                    obj.toAddress(pathArray[0], $label);
4369
4370                    if (coords.length > 0) {
4371                        var polygon;
4372
4373                        if (obj.drawingMode == "Polygon") {
4374
4375                            var polygon = new google.maps.Polygon({
4376                                path: pathArray,
4377                                clickable: true,
4378                                editable: true,
4379                                strokeColor: obj.strokeColor,
4380                                fillColor: obj.fillColor,
4381                                strokeWeight: 2
4382                            });
4383                            polygon.setMap(map);
4384
4385                            // Select the polygon
4386                            obj.setSelection(polygon, google.maps.drawing.OverlayType.POLYGON);
4387
4388                            // Disable the drawing manager
4389                            obj.disableDrawingManager();
4390
4391                            // add listener for moving polygon points.
4392                            google.maps.event.addListener(polygon.getPath(), 'set_at', function (e) {
4393                                obj.setSelection(polygon, google.maps.drawing.OverlayType.POLYGON);
4394                            });
4395
4396                            // add listener for adding new points.
4397                            google.maps.event.addListener(polygon.getPath(), 'insert_at', function (e) {
4398                                obj.setSelection(polygon, google.maps.drawing.OverlayType.POLYGON);
4399                            });
4400
4401                            // Add an event listener to implement right-click to delete node
4402                            google.maps.event.addListener(polygon, 'rightclick', function (ev) {
4403                                if (ev.vertex != null) {
4404                                    polygon.getPath().removeAt(ev.vertex);
4405                                }
4406                                obj.setSelection(polygon, google.maps.drawing.OverlayType.POLYGON);
4407                            });
4408
4409                            // add listener for "selecting" the polygon
4410                            // because that's where we stuff the coordinates into the hidden variable
4411                            google.maps.event.addListener(polygon, 'click', function () {
4412                                obj.setSelection(polygon, google.maps.drawing.OverlayType.POLYGON);
4413                            });
4414                        }
4415                        else if (obj.drawingMode == "Point") {
4416                            var point;
4417
4418                            if (!obj.mapId) {
4419                                point = new google.maps.Marker({
4420                                    position: pathArray[0],
4421                                    map: map,
4422                                    clickable: true,
4423                                    icon: obj.getMarkerImage()
4424                                });
4425                            }
4426                            else {
4427                                var pin = new google.maps.marker.PinElement({
4428                                    background: '#FE7569',
4429                                    borderColor: '#000',
4430                                    scale: 1,
4431                                    glyph: ''
4432                                });
4433
4434                                point = {
4435                                    position: pathArray[0],
4436                                    map: map,
4437                                    clickable: true,
4438                                    icon: obj.getMarkerImage(),
4439                                    marker_element: new google.maps.marker.AdvancedMarkerElement({
4440                                        position: pathArray[0],
4441                                        map: map,
4442                                        content: pin.element
4443                                    }),
4444                                    setMap: function (newMap) {
4445                                        this.map = newMap;
4446                                        this.marker_element.map = newMap;
4447                                    },
4448                                    getPosition: function () {
4449                                        return this.position;
4450                                    },
4451                                    setPosition: function (newPosition) {
4452                                        this.position = newPosition;
4453                                        this.marker_element.position = newPosition;
4454                                    }
4455                                };
4456                            }
4457
4458                            // Select the point
4459                            obj.setSelection(point, google.maps.drawing.OverlayType.MARKER);
4460
4461                            // Disable the drawing manager
4462                            obj.disableDrawingManager();
4463
4464                            // add listener for "selecting" the point
4465                            // because that's where we stuff the coordinates into the hidden variable
4466                            google.maps.event.addListener(point, 'click', function () {
4467                                obj.setSelection(point, google.maps.drawing.OverlayType.MARKER);
4468                            });
4469                        }
4470                    }
4471                }
4472            }
4473
4474            /**
4475            * Take the given lat and long and try to reverse-geocode to an Address
4476            * then stuff that address as the value of the given element.
4477            */
4478            this.toAddress = function (latlng, $labelElement) {
4479                // only try if we have a valid latlng
4480                if (!latlng || isNaN(latlng.lat()) || isNaN(latlng.lng())) {
4481                    $labelElement.text('');
4482                    return;
4483                }
4484                var geocoder = new google.maps.Geocoder();
4485                geocoder = geocoder.geocode({ "latLng": latlng }, function (results, status) {
4486                    if (status == google.maps.GeocoderStatus.OK) {
4487                        if (results[0]) {
4488                            $labelElement.text('near ' + results[0].formatted_address);
4489                        }
4490                    }
4491                    else
4492                    {
4493                        $labelElement.html('<i>selected</i>');
4494                        console.log('Geocoder failed due to: ' + status);
4495                    }
4496                });
4497            }
4498
4499            /**
4500            * Geocode an address to a latLng and center the map on that point.
4501            */
4502            this.centerMapOnAddress = function () {
4503                var self = this;
4504                // only try if a centerAddress is set
4505                if (!obj.centerAddress) {
4506                    return;
4507                }
4508
4509                var geocoder = new google.maps.Geocoder();
4510
4511                geocoder.geocode({ 'address': obj.centerAddress }, function (results, status) {
4512                    if (status == google.maps.GeocoderStatus.OK) {
4513                        self.map.setCenter(results[0].geometry.location);
4514                    } else {
4515                        console.log('Geocode was not successful for the following reason: ' + status);
4516                    }
4517                });
4518            }
4519
4520            /**
4521            * Return a latLng for the "first point" of the selected map.
4522            */
4523            this.firstPoint = function () {
4524                if (obj.path) {
4525                    var coords = obj.path.split('|');
4526                    if (coords) {
4527                        var latLng = coords[0].split(',');
4528                        var lat = parseFloat(latLng[0]);
4529                        var lng = parseFloat(latLng[1]);
4530                        return new google.maps.LatLng(lat, lng);
4531                    }
4532                }
4533            }
4534
4535        }; // sorta end class
4536
4537        /**
4538        * initialize our event handlers for the button clicks we're going to be handling.
4539        */
4540        GeoPicker.prototype.initializeEventHandlers = function () {
4541            var controlId = this.controlId,
4542                $control = $('#' + this.controlId),
4543                $hiddenField = $('#' + this.controlId + '_hfGeoPath'),
4544                self = this;
4545
4546            /**
4547            * Toggle the picker on and off when the control's link is clicked.
4548            */
4549            $('#' + controlId + ' .picker-label').on('click', function (e) {
4550                e.preventDefault();
4551                var $control = $('#' + controlId);
4552                $(this).toggleClass("active");
4553                $control.find('.picker-menu').first().toggle(0, function () {
4554                    Rock.dialogs.updateModalScrollBar(controlId);
4555                });
4556
4557                if ( $control.find('.picker-menu').first().is(":visible") ) {
4558                    google.maps.event.trigger(self.map, "resize");
4559                    // now we can safely fit the map to any polygon boun
4559dary
4560                    self.fitBounds();
4561
4562                    // Scroll down so you can see the done/cancel buttons
4563                    $("html,body").animate({
4564                        scrollTop: $control.offset().top
4565                    }, 1000);
4566                }
4567            });
4568
4569            /**
4570            * Hide the expand button if we are in a modal
4571            */
4572            if ($control.closest('.modal').length) {
4573                $('#btnExpandToggle_' + controlId).hide();
4574            }
4575
4576            /**
4577            * Handle the toggle expand fullscreen button click.
4578            */
4579            $('#btnExpandToggle_' + controlId).on('click', function () {
4580
4581                var $myElement = $('#geoPicker_' + self.controlId);
4582
4583                var isExpaned = $myElement.data("fullscreen");
4584
4585                $(this).children('i').toggleClass("ti-maximize", isExpaned);
4586                $(this).children('i').toggleClass("ti-minimize", ! isExpaned);
4587
4588                // Shrink to regular size
4589                if ( isExpaned ) {
4590                    $myElement.data("fullscreen", false);
4591
4592                    $(this).closest('.picker-menu').css({
4593                        position: 'absolute',
4594                        top: 0,
4595                        left: 0,
4596                        height: '',
4597                        width: 520
4598                    });
4599                    // resize the map
4600                    $myElement.css({
4601                        height: 300,
4602                        width: 500
4603                    });
4604
4605                    // move the delete button
4606                    $('#gmnoprint-delete-button_' + self.controlId).css({
4607                        left: '200px',
4608                    });
4609
4610                }
4611                else {
4612
4613                    // Expand to fullscreen
4614
4615                    $myElement.data("fullscreen", true);
4616                    // resize the container
4617                    $(this).closest('.picker-menu').css({
4618                        position: 'fixed',
4619                        top: 0,
4620                        left: 0,
4621                        height: '100%',
4622                        width: '100%'
4623                    });
4624
4625                    // resize the map
4626                    $myElement.css({
4627                        height: '85%',
4628                        width: '100%'
4629                    });
4630
4631                    // move the delete button
4632                    $('#gmnoprint-delete-button_' + self.controlId).css({
4633                        left: '200px',
4634                    });
4635                }
4636
4637                // tell the map to resize/redraw
4638                google.maps.event.trigger(self.map, 'resize');
4639                self.fitBounds();
4640
4641                Rock.dialogs.updateModalScrollBar(self.controlId);
4642
4643            });
4644
4645            /**
4646            * Handle the Cancel button click by hiding the overlay.
4647            */
4648            $('#btnCancel_' + controlId).on('click', function () {
4649                $(this).closest('.picker-menu').slideUp(function () {
4650                    Rock.dialogs.updateModalScrollBar(controlId);
4651                });
4652
4653                self.path = self.pathTemp;
4654
4655                if ( self.selectedShape ) {
4656                    self.selectedShape.setMap(null);
4657                    self.clearSelection();
4658
4659                    // enable the drawing controls again
4660                    self.drawingManager.setOptions({
4661                        drawingControlOptions: {
4662                            drawingModes: self.getDrawingModes()
4663                        }
4664                    });
4665                }
4666                self.plotPath(self.map);
4667            });
4668
4669            // have the X appear on hover if something is selected
4670            if ($hiddenField.val() && $hiddenField.val() !== '0') {
4671                $control.find('.picker-select-none').addClass('rollover-item').show();
4672            }
4673
4674            /**
4675            * Handle the Select button click by stuffing the RockGoogleGeoPicker's path value into the hidden field.
4676            */
4677            $control.find('.js-geopicker-select').on('click', function () {
4678                var geoInput = $('#' + controlId).find('input:checked'),
4679                    selectedValue = self.path,
4680                    selectedGeographyLabel = $('#selectedGeographyLabel_' + controlId);
4681
4682                $hiddenField.val(self.path);
4683
4684                // have the X appear on hover. something is selected
4685                $control.find('.picker-select-none').addClass('rollover-item');
4686                $control.find('.picker-select-none').show();
4687
4688                selectedGeographyLabel.val(selectedValue);
4689                self.toAddress( self.firstPoint(), selectedGeographyLabel);
4690
4691                //clear out any old map positioning
4692                self.initMinMaxLatLng();
4693
4694                $(this).closest('.picker-menu').slideUp(function () {
4695                    Rock.dialogs.updateModalScrollBar(controlId);
4696                });
4697
4698                $(this).closest('form').submit();
4699            });
4700
4701
4702            /**
4703            * Clear the selection when X is clicked
4704            */
4705            $control.find('.picker-select-none').on('click', function (e) {
4706                e.stopImmediatePropagation();
4707                var selectedGeographyLabel = $('#selectedGeographyLabel_' + controlId);
4708                $hiddenField.val("");
4709
4710                // don't have the X appear on hover. nothing is selected
4711                $control.find('.picker-select-none').removeClass('rollover-item');
4712                $control.find('.picker-select-none').hide();
4713
4714                selectedGeographyLabel.val("");
4715                self.toAddress(null, selectedGeographyLabel);
4716
4717                //clear out any old map positioning
4718                self.initMinMaxLatLng();
4719
4720                return false;
4721            });
4722        };
4723
4724        /**
4725        * Initialize the GeoPicker.
4726        */
4727        GeoPicker.prototype.initialize = function () {
4728            var self = this;
4729            var $hiddenField = $('#' + self.controlId + '_hfGeoPath');
4730            var deleteButtonId = 'gmnoprint-delete-button_' + self.controlId;
4731
4732            // Pull anything in the hidden field onto this object's path
4733            self.path = $hiddenField.val();
4734
4735            // store path into pathTemp in case of user cancel.
4736            self.pathTemp = self.path;
4737
4738            // Create a new StyledMapType object, passing it the array of styles,
4739            // as well as the name to be displayed on the map type control.
4740            var styledMap = new google.maps.StyledMapType(self.styles, { name: "Styled Map" });
4741
4742            // WARNING: I though about removing the "center:" from the options here but then the
4743            // map's controls were different and then our delete button was out of alignment.
4744            var mapOptions = {
4745                center: new google.maps.LatLng(
4746                        parseFloat(self.centerLatitude),
4747                        parseFloat(self.centerLongitude)),
4748                zoom: 13,
4749                streetViewControl: false,
4750                mapTypeControlOptions: {
4751                    mapTypeIds: [google.maps.MapTypeId.ROADMAP, 'map_style']
4752                },
4753            };
4754
4755            if (self.mapId) {
4756                mapOptions.mapId = self.mapId;
4757            }
4758
4759            // center the map on the configured address
4760            self.centerMapOnAddress();
4761
4762            self.map = new google.maps.Map(document.getElementById('geoPicker_' + self.controlId), mapOptions);
4763
4764            //Associate the styled map with the MapTypeId and set it to display.
4765            self.map.mapTypes.set('map_style', styledMap);
4766            self.map.setMapTypeId('map_style');
4767
4768            // If we have coordinates we should plot them here...
4769            self.plotPath(self.map);
4770
4771            // Set up the Drawing Manager for creating polygons, circles, etc.
4772            self.drawingManager = new google.maps.drawing.DrawingManager({
4773                drawingControl: true,
4774                drawingControlOptions: {
4775                    drawingModes: self.getDrawingModes()
4776                },
4777                polygonOptions: {
4778                    editable: true,
4779                    strokeColor: self.strokeColor,
4780                    fillColor: self.fillColor,
4781                    strokeWeight: 2
4782                },
4783                markerOptions: {
4784                    icon: self.getMarkerImage()
4785                }
4786            });
4787
4788            self.drawingManager.setMap(self.map);
4789
4790            // but disable the drawing manager if we already have a point/polygon selected:
4791            if (self.path) {
4792                self.disableDrawingManager();
4793            }
4794
4795            // Handle when the polygon shape drawing is "complete"
4796            google.maps.event.addListener(self.drawingManager, 'overlaycomplete', function (e) {
4797                if (e.type == google.maps.drawing.OverlayType.POLYGON || e.type == google.maps.drawing.OverlayType.MARKER ) {
4798                    // Disable the drawing manager once they've drawn an overlay.
4799                    self.disableDrawingManager();
4800
4801                    // Add an event listener that selects the newly-drawn shape when the user mouses down on it.
4802                    var newShape = e.overlay;
4803                    newShape.type = e.type;
4804                    google.maps.event.addListener(newShape, 'click', function () {
4805                        self.setSelection(newShape, e.type);
4806                    });
4807                    self.setSelection(newShape, e.type);
4808
4809                    if (e.type == google.maps.drawing.OverlayType.POLYGON) {
4810                        // add listener for moving polygon points.
4811                        google.maps.event.addListener(newShape.getPath(), 'set_at', function (e) {
4812                            self.setSelection(newShape, google.maps.drawing.OverlayType.POLYGON);
4813                        });
4814
4815                        // add listener for adding new points.
4816                        google.maps.event.addListener(newShape.getPath(), 'insert_at', function (e) {
4817                            self.setSelection(newShape, google.maps.drawing.OverlayType.POLYGON);
4818                        });
4819
4820                        // Add an event listener to implement right-click to delete node
4821                        google.maps.event.addListener(newShape, 'rightclick', function (ev) {
4822                            if (ev.vertex != null) {
4823                                newShape.getPath().removeAt(ev.vertex);
4824                            }
4825                            self.setSelection(newShape, google.maps.drawing.OverlayType.POLYGON);
4826                        });
4827                    }
4828                }
4829            });
4830
4831            // Clear the current selection when the drawing mode is changed, or when the map is clicked.
4832            google.maps.event.addListener(self.drawingManager, 'drawingmode_changed', self.clearSelection);
4833            google.maps.event.addListener(self.map, 'click', self.clearSelection);
4834
4835            // Move our custom delete button into place once the map is idle.
4836            // as per http://stackoverflow.com/questions/832692/how-to-check-if-google-maps-is-fully-loaded
4837            google.maps.event.addListenerOnce(self.map, 'idle', function () {
4838                $('#' + deleteButtonId).fadeIn();
4839
4840                // wire up an event handler to the delete button
4841                document.getElementById(deleteButtonId).addEventListener('click', self.deleteSelectedShape);
4842            });
4843
4844            self.initializeEventHandlers();
4845
4846            Rock.dialogs.updateModalScrollBar(self.controlId);
4847        };
4848
4849        var exports = {
4850            googleMapsLoadCallback: function () {
4851                // callback for when google maps api is done loading (if it wasn't loaded already)
4852                $.each(Rock.controls.geoPicker.geoPickerOptions, function (a, options) {
4853                    var geoPicker = Rock.controls.geoPicker.geoPickers[options.controlId];
4854                    if (!geoPicker) {
4855
4856                        geoPicker = new GeoPicker(options);
4857                        Rock.controls.geoPicker.geoPickers[options.controlId] = geoPicker;
4858                        geoPicker.initialize();
4859                    }
4860                });
4861            },
4862            geoPickers: {},
4863            geoPickerOptions: {},
4864            findControl: function (controlId) {
4865                return exports.geoPickers[controlId];
4866            },
4867            initialize: function (options) {
4868                if (!options.controlId) throw '`controlId` is required.';
4869                exports.geoPickerOptions[options.controlId] = options;
4870
4871                $(window).on('googleMapsIsLoaded', this.googleMapsLoadCallback);
4872
4873                // if the google maps api isn't loaded uet, googleMapsLoadCallback will take care of it
4874                if (typeof (google) != "undefined") {
4875                    // null it out just in case, to force it to get recreated
4876                    exports.geoPickers[options.controlId] = null;
4877                    $(window).trigger('googleMapsIsLoaded');
4878                }
4879            }
4880        };
4881
4882        return exports;
4883    }());
4884}(jQuery));
4885;
4886(function (Sys) {
4887    'use strict';
4888    Sys.Application.add_load(function () {
4889        $('a.help').on('click', function (e) {
4890            e.preventDefault();
4891            $(this).siblings('div.alert-info').slideToggle(function () {
4892                Rock.controls.modal.updateSize(this);
4893            });
4894            $(this).siblings('a.warning').insertAfter($(this));
4895            $(this).siblings('div.alert-warning').slideUp();
4896        });
4897    });
4898}(Sys));
4899;
4900(function ($) {
4901    'use strict';
4902    window.Rock = window.Rock || {};
4903
4904    Rock.htmlEditor = (function () {
4905        var exports = {
4906            // full list of available items is at http://summernote.org/deep-dive/#custom-toolbar-popover
4907
4908            // the toolbar items to include in the HtmlEditor when Toolbar is set to Light 
4909            toolbar_RockCustomConfigLight:
4910                [
4911                    // [groupName, [list of button]]
4912                    ['source_group', ['rockcodeeditor']],
4913                    ['style_group1', ['bold', 'italic', 'strikethrough', 'link', 'color', 'style', 'ol', 'ul']],
4914                    ['style_group3', ['clear']],
4915                    ['para', ['paragraph']],
4916                    ['plugins1', ['rockmergefield']],
4917                    ['plugins2', ['rockimagebrowser', 'rockfilebrowser', 'rockassetmanager']],
4918                    ['plugins3', ['rockpastetext', 'rockpastefromword']],
4919                    ['style_group2', ['undo', 'redo']]
4920                ],
4921
4922            // the toolbar items to include in the HtmlEditor when Toolbar is set to Full 
4923            toolbar_RockCustomConfigFull:
4924                [
4925                    // [groupName, [list of button]]
4926                    ['source_group', ['rockcodeeditor']],
4927                    ['style_group1', ['bold', 'italic', 'underline', 'strikethrough', 'ol', 'ul', 'link']],
4928                    ['style_group2', ['undo', 'redo']],
4929                    ['style_group3', ['clear']],
4930                    ['style_group4', ['style', 'color']],
4931                    ['full_toolbar_only', ['fontname', 'fontsize', 'superscript', 'subscript', 'table', 'hr']],
4932                    ['para', ['paragraph']],
4933                    ['plugins1', ['rockmergefield']],
4934                    ['plugins2', ['rockimagebrowser', 'rockfilebrowser', 'rockassetmanager']],
4935                    ['plugins3', ['rockpastetext', 'rockpastefromword']],
4936                    ['help_group1', ['help']]
4937                ]
4938        };
4939
4940        return exports;
4941
4942    }());
4943}(jQuery));
4944;
4945(function ($) {
4946    'use strict';
4947    window.Rock = window.Rock || {};
4948    Rock.controls = Rock.controls || {};
4949
4950    Rock.controls.imageUploader = (function () {
4951        var _configure = function (options) {
4952            options.isBinaryFile = options.isBinaryFile || 'T';
4953
4954            var $contentFileSource = $('#' + options.hfContentFileSource);
4955            var $binaryFileId = $('#' + options.hfFileId);
4956
4957            // default setImageUrlOnUpload to true if not specified
4958            if (options.setImageUrlOnUpload == null) {
4959                options.setImageUrlOnUpload = true;
4960            }
4961
4962            var wsUrl = Rock.settings.get('baseUrl')
4963                + 'ImageUploader.ashx?'
4964                + 'isBinaryFile=' + options.isBinaryFile;
4965
4966            if (options.isBinaryFile == 'T') {
4967                wsUrl += '&fileId=' + options.fileId
4968                    + '&fileTypeGuid=' + options.fileTypeGuid;
4969            }
4970            else {
4971                // note rootFolder is encrypted to prevent direct access to filesystem via the URL
4972                wsUrl += '&rootFolder=' + (encodeURIComponent(options.rootFolder) || '');
4973            }
4974
4975            if (options.isTemporary == 'F') {
4976                wsUrl += '&IsTemporary=False';
4977            }
4978
4979            // uses https://github.com/blueimp/jQuery-File-Upload
4980            $('#' + options.controlId).fileupload({
4981                url: wsUrl,
4982                dataType: 'json',
4983                dropZone: $('#' + options.controlId).closest('.imageupload-dropzone'),
4984                autoUpload: true,
4985                submit: options.submitFunction,
4986                start: function (e, data) {
4987                    var $el = $('#' + options.controlId).closest('.imageupload-group');
4988                    $el.find('.js-upload-progress').rockFadeIn();
4989                },
4990                progressall: function (e, data) {
4991                    // var $el = $('#' + options.controlId).closest('.imageupload-group');
4992                    // implement this to show progress percentage
4993                },
4994                stop: function (e) {
4995                    var $el = $('#' + options.controlId).closest('.imageupload-group');
4996                    $el.find('.js-upload-progress').hide();
4997                    $el.find('.imageupload-dropzone').rockFadeIn();
4998                },
4999                done: function (e, data) {
5000                    var $el = $('#' + options.imgThumbnail);
5001
5002                    if ((options.isBinaryFile || 'T') == 'F') {
5003                        $contentFileSource.val(data.response().result.FileName);
5004                    }
5005                    else {
5006                        $binaryFileId.val(data.response().result.Id);
5007                    }
5008
5009                    var getImageUrl = Rock.settings.get('baseUrl')
5010                        + 'GetImage.ashx?'
5011                        + 'isBinaryFile=' + (options.isBinaryFile || 'T');
5012
5013                    if (options.disablePredictableIds) {
5014                        getImageUrl += '&guid=' + data.response().result.Guid
5015                    }
5016                    else {
5017                        getImageUrl += '&id=' + data.response().result.Id;
5018                    }
5019
5020                    getImageUrl += '&fileName=' + data.response().result.FileName
5021                        + '&width=500';
5022
5023                    if (options.rootFolder) {
5024                        // note rootFolder is encrypted to prevent direct access to filesystem via the URL
5025                        getImageUrl += '&rootFolder=' + encodeURIComponent(options.rootFolder);
5026                    }
5027
5028                    if (options.setImageUrlOnUpload) {
5029                        if ($el.is('img')) {
5030                            $el.attr('src', getImageUrl);
5031                        }
5032                        else {
5033                            $el.attr('style', 'background-image:url("' + getImageUrl + '");background-size:cover;background-position:50%');
5034                        }
5035                    }
5036
5037                    $('#' + options.aRemove).show();
5038                    if (options.postbackScript) {
5039                        window.location = "javascript:" + options.postbackScript;
5040                    }
5041
5042                    if (options.doneFunction) {
5043                        options.doneFunction(e, data);
5044                    }
5045                },
5046                fail: function (e, data) {
5047                    var $el = $('#' + options.controlId).closest('.imageupload-group');
5048                    $el.siblings('.js-rockupload-alert').remove();
5049                    var $warning = $('<div class="alert alert-warning alert-dismissable js-rockupload-alert"/>');
5050
5051                    var msg = "unable to upload";
5052                    if (data.response().jqXHR && data.response().jqXHR.status == 406) {
5053                        msg = "file type not allowed";
5054                    } else if (data.response().jqXHR && data.response().jqXHR.responseText) {
5055                        msg = data.response().jqXHR.responseText;
5056                    }
5057
5058                    if (options.maxUploadBytes && data.total) {
5059                        if (data.total >= options.maxUploadBytes) {
5060                            msg = "file size is limited to " + (options.maxUploadBytes / 1024 / 1024) + "MB";
5061                        }
5062                    }
5063
5064                    $warning.append('<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>')
5065                        .append('<strong><i class="ti ti-alert-triangle"></i> Warning </strong>')
5066                        .append(msg);
5067                    $warning.insertBefore($el);
5068                }
5069            });
5070
5071            $('#' + options.aRemove).on('click', function () {
5072                $(this).hide();
5073                var $el = $('#' + options.imgThumbnail);
5074                var noPictureUrl = options.noPictureUrl || Rock.settings.get('baseUrl') + 'Assets/Images/no-picture.svg';
5075                if ($el.is('img')) {
5076                    $el.attr('src', noPictureUrl);
5077                }
5078                else {
5079                    $el.attr('style', 'background-image:url(' + noPictureUrl + ');background-size:cover;background-position:50%');
5080                }
5081
5082                if (options.deleteFunction) {
5083                    options.deleteFunction();
5084                }
5085
5086                if (options.postbackRemovedScript) {
5087                    window.location = "javascript:" + options.postbackRemovedScript;
5088                } else {
5089                    $binaryFileId.val('0');
5090                    $contentFileSource.val('');
5091                }
5092                return false;
5093            });
5094        },
5095        exports = {
5096            initialize: function (options) {
5097                if (!options.controlId) throw 'Control ID must be set.';
5098                _configure(options);
5099            }
5100        };
5101
5102        return exports;
5103    }());
5104}(jQuery));
5105;
5106(function ($) {
5107    'use strict';
5108    window.Rock = window.Rock || {};
5109    Rock.controls = Rock.controls || {};
5110
5111    Rock.controls.itemPicker = (function () {
5112        var ItemPicker = function (options) {
5113            this.options = options;
5114
5115            // set a flag so that the picker only auto-scrolls to a selected item once. This prevents it from scrolling at unwanted times
5116            this.alreadyScrolledToSelected = false;
5117            this.iScroll = null;
5118        },
5119            exports;
5120
5121        ItemPicker.prototype = {
5122            constructor: ItemPicker,
5123            initialize: function () {
5124                var $control = $('#' + this.options.controlId),
5125                    $tree = $control.find('.treeview'),
5126                    treeOptions = {
5127                        multiselect: this.options.allowMultiSelect,
5128                        categorySelection: this.options.allowCategorySelection,
5129                        categoryPrefix: this.options.categoryPrefix,
5130                        restUrl: this.options.restUrl,
5131                        context: this.options.context,
5132                        restParams: this.options.restParams,
5133                        expandedIds: this.options.expandedIds,
5134                        expandedCategoryIds: this.options.expandedCategoryIds,
5135                        showSelectChildren: this.options.showSelectChildren,
5136                        id: this.options.startingId
5137                    },
5138                    $hfItemIds = $control.find('.js-item-id-value'),
5139                    $hfExpandedIds = $control.find('.js-initial-item-parent-ids-value'),
5140                    $hfExpandedCategoryIds = $control.find('.js-expanded-category-ids');
5141
5142                if (typeof this.options.mapItems === 'function') {
5143                    treeOptions.mapping = {
5144                        mapData: this.options.mapItems
5145                    };
5146                }
5147
5148                // clean up the tree (in case it was initialized already, but we are rebuilding it)
5149                var rockTree = $tree.data('rockTree');
5150                if (rockTree) {
5151                    rockTree.nodes = [];
5152                }
5153                $tree.empty();
5154
5155                var $scrollContainer = $control.find('.scroll-container .viewport');
5156                var $scrollIndicator = $control.find('.track');
5157                this.iScroll = new IScroll($scrollContainer[0], {
5158                    mouseWheel: true,
5159                    indicators: {
5160                        el: $scrollIndicator[0],
5161                        interactive: true,
5162                        resize: false,
5163                        listenY: true,
5164                        listenX: false,
5165                    },
5166                    click: false,
5167                    preventDefaultException: { tagName: /.*/ }
5168                });
5169
5170                // Since some handlers are "live" events, they need to be bound before tree is initialized
5171                this.initializeEventHandlers();
5172
5173                if ($hfItemIds.val() && $hfItemIds !== '0') {
5174                    treeOptions.selectedIds = $hfItemIds.val().split(',');
5175                }
5176
5177                if ($hfExpandedIds.val()) {
5178                    treeOptions.expandedIds = $hfExpandedIds.val().split(',');
5179                }
5180
5181                if ($hfExpandedCategoryIds.val()) {
5182                    treeOptions.expandedCategoryIds = $hfExpandedCategoryIds.val().split(',');
5183                }
5184
5185                if (this.options.universalItemPicker) {
5186                    function mapUniversalItems(data) {
5187                        return data.map(item => {
5188                            const treeItem = {
5189                                id: item.value,
5190                                name: item.text,
5191                                iconCssClass: item.iconCssClass,
5192                                hasChildren: item.hasChildren,
5193                                isCategory: false,
5194                                isSelectionDisabled: item.isFolder,
5195                                childrenUrl: item.childrenUrl
5196                            };
5197
5198                            if (Array.isArray(item.children)) {
5199                                treeItem.children = mapUniversalItems(item.children);
5200                            }
5201
5202                            return treeItem;
5203                        });
5204                    }
5205
5206                    treeOptions.universalItemPicker = true;
5207                    treeOptions.expandedIds = (treeOptions.expandedIds || []).filter(id => id !== 0);
5208
5209                    treeOptions.getNodes = (parentId, parentNode, selectedIds, toExpandIds) => {
5210                        const req = {
5211                            context: treeOptions.context
5212                        };
5213
5214                        if (!parentId) {
5215                            req.expandToValues = (selectedIds || []).filter(a => a !== "0");
5216                        }
5217                        else {
5218                            req.parentValue = parentId;
5219                        }
5220
5221                        return $.ajax({
5222                            method: 'POST',
5223                            data: JSON.stringify(req),
5224                            url: treeOptions.restUrl,
5225                            dataType: 'json',
5226                            contentType: 'application/json'
5227                        })
5228                            .then(data => {
5229                                function checkItemsForExpansion(items, path) {
5230                                    for (let i = 0; i < items.length; i++) {
5231                                        if (selectedIds.some(id => id === items[i].value)) {
5232                                            for (let p = 0; p < path.length; p++) {
5233                                                if (!toExpandIds.includes(path[p])) {
5234                                                    toExpandIds.push(path[p]);
5235                                                }
5236                                            }
5237                                        }
5238
5239                                        if (Array.isArray(items[i].children)) {
5240                                            checkItemsForExpansion(items[i].children, [...path, items[i].value]);
5241                                        }
5242                                    }
5243                                }
5244
5245                                checkItemsForExpansion(data, []);
5246
5247                                return data;
5248                            });
5249                    };
5250
5251                    treeOptions.mapping = {
5252                        mapData: mapUniversalItems
5253                    };
5254                }
5255
5256                $tree.rockTree(treeOptions);
5257                this.updateScrollbar();
5258            },
5259            initializeEventHandlers: function () {
5260                var self = this,
5261                    $control = $('#' + this.options.controlId),
5262                    $spanNames = $control.find('.selected-names'),
5263                    $hfItemIds = $control.find('.js-item-id-value'),
5264                    $hfItemNames = $control.find('.js-item-name-value');
5265
5266                // Bind tree events
5267                $control.find('.treeview')
5268                    .on('rockTree:selected', function () {
5269                        // intentionally blank
5270                    })
5271                    .on('rockTree:itemClicked', function (e) {
5272                        // make sure it doesn't autoscroll after something has been manually clicked
5273                        self.alreadyScrolledToSelected = true;
5274                        if (!self.options.allowMultiSelect) {
5275                            $control.find('.picker-btn').trigger('click');
5276                        }
5277                    })
5278                    .on('rockTree:expand rockTree:collapse rockTree:dataBound', function (evt) {
5279                        self.updateScrollbar();
5280                    })
5281                    .on('rockTree:rendered', function (evt) {
5282                        self.scrollToSelectedItem();
5283                    });
5284
5285                $control.find('.picker-label').on('click', function (e) {
5286                    e.preventDefault();
5287                    $(this).toggleClass("active");
5288                    $control.find('.picker-menu').first().toggle(0, function () {
5289                        self.scrollToSelectedItem();
5290                    });
5291                });
5292
5293                $control.find('.picker-cancel').on('click', function () {
5294                    $(this).toggleClass("active");
5295                    $(this).closest('.picker-menu').toggle(0, function () {
5296                        self.updateScrollbar();
5297                    });
5298                    $(this).closest('.picker-label').toggleClass("active");
5299                });
5300
5301                // have the X appear on hover if something is selected
5302                if ($hfItemIds.val() && $hfItemIds.val() !== '0') {
5303                    $control.find('.picker-select-none').addClass('rollover-item');
5304                    $control.find('.picker-select-none').show();
5305                }
5306
5307                $control.find('.picker-btn').on('click', function (el) {
5308
5309                    var rockTree = $control.find('.treeview').data('rockTree'),
5310                        selectedNodes = rockTree.selectedNodes,
5311                        selectedIds = [],
5312                        selectedNames = [];
5313                    $.each(selectedNodes, function (index, node) {
5314                        var nodeName = $("<textarea/>").html(node.name).text();
5315                        selectedNames.push(nodeName);
5316                        if (!selectedIds.includes(node.id)) {
5317                            selectedIds.push(node.id);
5318                        }
5319                    });
5320
5321                    $hfItemIds.val(selectedIds.join(',')).trigger('change'); // .trigger('change') is used to cause jQuery to fire any "onchange" event handlers for this hidden field.
5322                    $hfItemNames.val(selectedNames.join(','));
5323
5324                    // have the X appear on hover. something is selected
5325                    $control.find('.picker-select-none').addClass('rollover-item');
5326                    $control.find('.picker-select-none').show();
5327
5328                    $spanNames.text(selectedNames.join(', '));
5329                    $spanNames.attr('title', $spanNames.text());
5330
5331                    $(this).closest('.picker-label').toggleClass("active");
5332                    $(this).closest('.picker-menu').toggle(0, function () {
5333                        self.updateScrollbar();
5334                    });
5335
5336                    $(this).trigger('onclick');
5337                    if (!(el && el.originalEvent && el.originalEvent.srcElement == this)) {
5338                        // if this event was called by something other than the button itself, make sure the execute the href (which is probably javascript)
5339                        var jsPostback = $(this).attr('href');
5340                        if (jsPostback) {
5341                            window.location = jsPostback;
5342                        }
5343                    }
5344                });
5345
5346                $control.find('.picker-select-none').on("click", function (e) {
5347                    e.preventDefault();
5348                    e.stopImmediatePropagation();
5349
5350                    var rockTree = $control.find('.treeview').data('rockTree');
5351                    rockTree.clear();
5352                    $hfItemIds.val('0').trigger('change'); // .trigger('change') is used to cause jQuery to fire any "onchange" event handlers for this hidden field.
5353                    $hfItemNames.val('');
5354
5355                    // don't have the X appear on hover. nothing is selected
5356                    $control.find('.picker-select-none').removeClass('rollover-item').hide();
5357
5358                    $control.siblings('.js-hide-on-select-none').hide();
5359
5360                    $spanNames.text(self.options.defaultText);
5361                    $spanNames.attr('title', $spanNames.text());
5362                    $(this).trigger('onclick');
5363                });
5364
5365                // clicking on the 'select all' btn
5366                $control.on('click', '.js-select-all', function (e)
5367                {
5368                  var rockTree = $control.find('.treeview').data('rockTree');
5369
5370                  e.preventDefault();
5371                  e.stopPropagation();
5372
5373                  var $itemNameNodes = rockTree.$el.find('.rocktree-name');
5374
5375                  var allItemNodesAlreadySelected = true;
5376                  $itemNameNodes.each(function (a)
5377                  {
5378                    if (!$(this).hasClass('selected')) {
5379                      allItemNodesAlreadySelected = false;
5380                    }
5381                  });
5382
5383                  if (!allItemNodesAlreadySelected) {
5384                    // mark them all as unselected (just in case some are selected already), then click them to select them
5385                    $itemNameNodes.removeClass('selected');
5386                    $itemNameNodes.trigger('click');
5387                  } else {
5388                    // if all were already selected, toggle them to unselected
5389                    rockTree.setSelected([]);
5390                    $itemNameNodes.removeClass('selected');
5391                  }
5392                });
5393            },
5394            updateScrollbar: function (sPosition) {
5395                var self = this;
5396                // first, update this control's scrollbar, then the modal'
5396s
5397                var $container = $('#' + this.options.controlId).find('.scroll-container');
5398
5399                if ($container.is(':visible')) {
5400                    if (!sPosition) {
5401                        sPosition = 'relative'
5402                    }
5403                    if (self.iScroll) {
5404                        self.iScroll.refresh();
5405                    }
5406                }
5407
5408                // update the outer modal
5409                Rock.dialogs.updateModalScrollBar(this.options.controlId);
5410            },
5411            scrollToSelectedItem: function () {
5412                var $selectedItem = $('#' + this.options.controlId).find('.picker-menu').find('.selected').first();
5413                if ($selectedItem.length && (!this.alreadyScrolledToSelected)) {
5414                    this.updateScrollbar();
5415                    this.iScroll.scrollToElement('.selected', '0s');
5416                    this.alreadyScrolledToSelected = true;
5417                } else {
5418                    // initialize/update the scrollbar
5419                    this.updateScrollbar();
5420                }
5421            }
5422        };
5423
5424        exports = {
5425            defaults: {
5426                id: 0,
5427                controlId: null,
5428                universalItemPicker: false,
5429                restUrl: null,
5430                restParams: null,
5431                allowCategorySelection: false,
5432                categoryPrefix: '',
5433                allowMultiSelect: false,
5434                defaultText: '',
5435                selectedIds: null,
5436                expandedIds: null,
5437                expandedCategoryIds: null,
5438                showSelectChildren: false
5439            },
5440            controls: {},
5441            initialize: function (options) {
5442                var settings,
5443                    itemPicker;
5444
5445                if (!options.controlId) throw 'controlId must be set';
5446                if (!options.restUrl) throw 'restUrl must be set';
5447
5448                settings = $.extend({}, exports.defaults, options);
5449
5450                if (!settings.defaultText) {
5451                    settings.defaultText = exports.defaults.defaultText;
5452                }
5453
5454                itemPicker = new ItemPicker(settings);
5455                exports.controls[settings.controlId] = itemPicker;
5456                itemPicker.initialize();
5457            }
5458        };
5459
5460        return exports;
5461    }());
5462}(jQuery));
5463;
5464; (function () {
5465    function updateKeyValues(e) {
5466        var $span = e.closest('span.key-value-list');
5467        var newValue = '';
5468
5469        $span.children('span.key-value-rows:first').children('div.controls-row').each(function (index) {
5470            if (newValue !== '') {
5471                newValue += '|';
5472            }
5473
5474            var keyValue = $(this).children('.key-value-key').first().val();
5475            var valueValue = $(this).children('.key-value-value').first().val();
5476
5477            // if the key or value have any magic chars ('^' or '|' or ','' ), URI encode each magic char using encodeURIComponent so that commas also get encoded
5478            var keyValueDelimiters = ['^', '|', ','];
5479            keyValueDelimiters.forEach(function (v, i, a) {
5480                var re = new RegExp('\\' + v, 'g');
5481                if (keyValue.indexOf(v) > -1) {
5482                    keyValue = keyValue.replace(re, encodeURIComponent(v));
5483                }
5484                if (valueValue.indexOf(v) > -1) {
5485                    valueValue = valueValue.replace(re, encodeURIComponent(v));
5486                }
5487            });
5488
5489            newValue += keyValue + '^' + valueValue;
5490        });
5491        $span.children('input').first().val(newValue);
5492
5493        raiseKeyValueList();
5494    }
5495
5496    function removeKeyValue(e) {
5497        var $rows = $(e).closest('span.key-value-rows');
5498        $(e).closest('div.controls-row').remove();
5499        Rock.controls.modal.updateSize($(e));
5500        updateKeyValues($rows);
5501    }
5502
5503    Sys.Application.add_load(function () {
5504
5505        $('a.key-value-add').on('click', function (e) {
5506            e.preventDefault();
5507            var $keyValueList = $(this).closest('.key-value-list');
5508            $keyValueList.find('.key-value-rows').append($keyValueList.find('.js-value-html').val());
5509            Rock.controls.modal.updateSize($(this));
5510            $('a.key-value-remove').off('click');
5511            $('a.key-value-remove').on('click', function (e) {
5512                e.preventDefault();
5513                removeKeyValue(this);
5514            });
5515            $('.js-key-value-input').off('change');
5516            $('.js-key-value-input').on('change', function (e) {
5517                e.preventDefault();
5518                updateKeyValues($(this));
5519            });
5520            updateKeyValues($(this));
5521        });
5522
5523        $('a.key-value-remove').off('click');
5524        $('a.key-value-remove').on('click', function (e) {
5525            e.preventDefault();
5526            removeKeyValue(this);
5527        });
5528
5529        $('.js-key-value-input').off('change');
5530        $('.js-key-value-input').on('change', function (e) {
5531            e.preventDefault();
5532            updateKeyValues($(this));
5533        });
5534    });
5535})();
5536;
5537(function ($) {
5538    'use strict';
5539    window.Rock = window.Rock || {};
5540    Rock.controls = Rock.controls || {};
5541
5542    Rock.controls.listItems = (function () {
5543        var exports = {
5544
5545            initialize: function (options) {
5546                if (!options.id) {
5547                    throw 'id is required';
5548                }
5549
5550                if (!options.clientId) {
5551                    throw 'clientId is required';
5552                }
5553
5554                const $listItemsControl = $('#' + options.clientId);
5555
5556                function updateListItemValues($e) {
5557                    var $span = $e.closest('span.list-items');
5558                    var keyValuePairs = [];
5559                    $span.children('span.list-items-rows').first().children('div.controls-row').each(function (index) {
5560                        keyValuePairs.push({
5561                            'Key': $(this).children('.input-group').find('.js-list-items-input').first().data('id'),
5562                            'Value': $(this).children('.input-group').find('.js-list-items-input').first().val()
5563                        });
5564                    });
5565                    $span.children('input').first().val(JSON.stringify(keyValuePairs));
5566                    if (options.valueChangedScript) {
5567                        window.location = "javascript:" + options.valueChangedScript;
5568                    }
5569
5570                }
5571
5572                var fixHelper = function (e, ui) {
5573                    ui.children().each(function () {
5574                        $(this).width($(this).width());
5575                    });
5576                    return ui;
5577                };
5578
5579                $listItemsControl.find('a.list-items-add').on('click', function (e) {
5580                    e.preventDefault();
5581                    $listItemsControl.find('.list-items-rows').append($listItemsControl.find('.js-list-items-html').val());
5582                    updateListItemValues($(this));
5583                    Rock.controls.modal.updateSize($(this));
5584                });
5585
5586                // Use add_load to fire with page load and postbacks
5587                Sys.Application.add_load(function () {
5588                    function onRemoveClick(event) {
5589                        event.preventDefault();
5590                        var $rows = $(this).closest('span.list-items-rows');
5591                        $(this).closest('div.controls-row').remove();
5592                        updateListItemValues($rows);
5593                        Rock.controls.modal.updateSize($(this));
5594                    }
5595
5596                    function onFocus(event) {
5597                        var element = event.target;
5598                        var $element = $(element);
5599                        var valueOnFocus = element.value;
5600
5601                        function onBlur() {
5602                            var valueOnBlur = element.value;
5603
5604                            if (valueOnFocus != valueOnBlur) {
5605                                updateListItemValues($element);
5606                            }
5607                        }
5608
5609                        // Only handle the focus out event once and remove the handler.
5610                        $element.one('blur', onBlur);
5611                    }
5612
5613                    $listItemsControl.find('a.list-items-remove').off('click', onRemoveClick).on('click', onRemoveClick);
5614                    $listItemsControl.find('.js-list-items-input').off('focus', onFocus).on('focus', onFocus);
5615                });
5616
5617                $listItemsControl.find('.list-items-rows').sortable({
5618                    helper: fixHelper,
5619                    handle: '.ti-menu-2',
5620                    start: function (event, ui) {
5621                        {
5622                            var start_pos = ui.item.index();
5623                            ui.item.data('start_pos', start_pos);
5624                        }
5625                    },
5626                    update: function (event, ui) {
5627                        updateListItemValues($(this));
5628                    }
5629
5630                }).disableSelection();
5631
5632            }
5633        };
5634
5635        return exports;
5636    }());
5637}(jQuery));
5638;
5639(function($) {
5640    window.Rock = window.Rock || {};
5641    window.Rock.controls = window.Rock.controls || {};
5642
5643    window.Rock.controls.mediaElementPicker = (function () {
5644        var exports = {
5645            initialize: function(options) {
5646                var $picker = $('#' + options.controlId);
5647
5648                $picker.data('required', options.required === true);
5649
5650                // Setup click handlers for any refresh buttons.
5651                $picker.find('.js-media-element-picker-refresh').on('click', function () {
5652                    if ($(this).hasClass('disabled')) {
5653                        return false;
5654                    }
5655
5656                    $picker.find('.js-media-element-picker-refresh').addClass('disabled');
5657                    $(this).find('i.ti').addClass('ti-spin');
5658
5659                    window.location = 'javascript:' + options.refreshScript;
5660
5661                    return false;
5662                });
5663
5664                // Re-validate after they change the selection of the media.
5665                $picker.find('.js-media-element-value select').on('change', function () {
5666                    ValidatorValidate(window[$picker.find('.js-media-element-validator').prop('id')]);
5667                });
5668            },
5669
5670            clientValidate: function (validator, args) {
5671                // After a postback, the validator is not actually in the DOM
5672                // anymore. So find the fake ancestor's Id and then lookup the
5673                // real media element picker.
5674                var mediaElementPickerId = $(validator).closest('.js-media-element-picker').attr('id');
5675                var $mediaElementPicker = $('#' + mediaElementPickerId);
5676
5677                var required = $mediaElementPicker.data('required') === true;
5678                var value = $mediaElementPicker.find('.js-media-element-value select').val() || '';
5679                var isValid = !required || (value !== '' && value !== '0');
5680
5681                if (isValid) {
5682                    $mediaElementPicker.closest('.form-group').removeClass('has-error');
5683                    args.IsValid = true;
5684                }
5685                else {
5686                    $mediaElementPicker.closest('.form-group').addClass('has-error');
5687                    args.IsValid = false;
5688                }
5689            }
5690        };
5691
5692        return exports;
5693    })();
5694})(jQuery);
5695;
5696(function ($) {
5697    'use strict';
5698    window.Rock = window.Rock || {};
5699    Rock.controls = Rock.controls || {};
5700
5701    Rock.controls.mediaPlayer = (function () {
5702        var exports = {
5703            initialize: function () {
5704
5705                Sys.Application.add_load(function () {
5706                    var cssFile = Rock.settings.get('baseUrl') + 'Scripts/mediaelementjs/mediaelementplayer.min.css';
5707                    var jsFile = Rock.settings.get('baseUrl') + 'Scripts/mediaelementjs/mediaelement-and-player.js';
5708
5709                    // ensure that css for mediaelementplayers is added to page
5710                    if (!$('#mediaElementCss').length) {
5711                        $('head').append("<link id='mediaElementCss' href='" + cssFile + "' type='text/css' rel='stylesheet' />");
5712                    }
5713
5714                    // ensure that js for mediaelementplayers is added to page
5715                    if (!$('#mediaElementJs').length) {
5716                        // by default, jquery adds a cache-busting parameter on dynamically added script tags. set the ajaxSetup cache:true to prevent this
5717                        $.ajaxSetup({ cache: true });
5718                        $('head').prepend("<script id='mediaElementJs' src='" + jsFile + "' />");
5719                    }
5720
5721                    // ensure that mediaelementplayer is applied to all the the rock audio/video that was generated from a Rock Video/Audio FieldType (js-media-audio,js-media-video)
5722                    $('audio.js-media-audio,video.js-media-video').mediaelementplayer({ enableAutosize: true });
5723                });
5724
5725            }
5726        };
5727
5728        return exports;
5729    }());
5730
5731    /** This is the new Rock media player that uses Plyr. */
5732    Rock.controls.mediaplayer = (function () {
5733        var exports = {
5734            initialize: function (options) {
5735                if (!options.id) {
5736                    throw "id is required";
5737                }
5738
5739                if (!options.playerId) {
5740                    throw "playerId is required";
5741                }
5742
5743                if (!options.progressId) {
5744                    throw "progressId is required";
5745                }
5746
5747                var $playerElement = $("#" + options.id);
5748                var playerOptions = $playerElement.data("player-options");
5749
5750                var player = new Rock.UI.MediaPlayer("#" + options.playerId, playerOptions);
5751
5752                player.on("ready", function () {
5753                    $("#" + options.progressId).val(player.percentWatched);
5754                });
5755
5756                player.on("progress", function () {
5757                    console.log("progress", player.percentWatched, options.progressId);
5758                    $("#" + options.progressId).val(player.percentWatched);
5759                });
5760
5761                $playerElement
5762                    .data("required", options.required || false)
5763                    .data("progress-id", options.progressId)
5764                    .data("required-percentage", options.requiredPercentage || 0)
5765                    .data("required-error-message", options.requiredErrorMessage || "You must watch the video.");
5766            },
5767
5768            clientValidate: function (validator, args) {
5769                var $playerElement = $(validator).closest(".js-media-player");
5770                var $formGroup = $playerElement.parent().hasClass("control-wrapper") ? $playerElement.closest(".form-group") : null;
5771                var progressId = $playerElement.data("progress-id");
5772                var required = $playerElement.data('required');
5773                var requiredProgress = $playerElement.data("required-percentage");
5774                var progress = parseFloat($("#" + progressId).val() || "0");
5775
5776                var isValid = !required || progress >= requiredProgress;
5777
5778                if (isValid) {
5779                    if ($formGroup) {
5780                        $formGroup.removeClass("has-error");
5781                    }
5782
5783                    args.IsValid = true;
5784                }
5785                else {
5786                    if ($formGroup) {
5787                        $formGroup.addClass("has-error");
5788                    }
5789
5790                    args.IsValid = false;
5791                    validator.errormessage = $playerElement.data('required-error-message');
5792                }
5793            }
5794        };
5795
5796        return exports;
5797    }());
5798}(jQuery));
5799;
5800(function ($) {
5801    'use strict';
5802    window.Rock = window.Rock || {};
5803    Rock.controls = Rock.controls || {};
5804
5805    Rock.controls.mediaSelector = (function () {
5806
5807        var exports = {
5808            clientValidate: function (validator, args) {
5809                var $checkBoxList = $(validator).closest('.js-mediaselector');
5810
5811                var checkboxes = $checkBoxList.find('input');
5812                var isValid = false;
5813                for (var i = 0; i < checkboxes.length; i++) {
5814                    if (checkboxes[i].checked) {
5815                        isValid = true;
5816                        break;
5817                    }
5818                }
5819
5820                if (isValid) {
5821                    $checkBoxList.removeClass('has-error');
5822                } else {
5823                    $checkBoxList.addClass('has-error');
5824                }
5825
5826                args.IsValid = isValid;
5827            }
5828        };
5829
5830        return exports;
5831    }());
5832}(jQuery));
5833
5834
5835;
5836/*
5837 * BJW  7-7-2020
5838 * Previous Issues and Fixes
5839 *
5840 * April 15, 2020
5841 * Problem:
5842 *      Open a modal dialog and close it. Next, try to open a modal alert. An invisible backdrop is added to the
5843 *      page such that the page cannot be interacted with. The alert does not appear.
5844 * Solution:
5845 *      Every postback causes the C# modal control code to register JS that checks a hidden field to determine
5846 *      if a modal should be open or closed. If many modals exist, then they are all checked and all call
5847 *      the JS close dialog method, which removed backdrops. If each modal removes any backdrop without
5848 *      discretion, then they interfere with each other. Therefore, only remove backdrops that belong to the modal
5849 *      in current context being closed.
5850 *      https://github.com/SparkDevNetwork/Rock/commit/8a1c5653c534c61548de34d0ca1a4483304437a1
5851 *
5852 * June 1, 2020
5853 * Problem:
5854 *      When a modal is opened, sometimes the body is set to .modal-open and sometimes a manager (usually the
5855 *      update panel of the block) is set to .modal-open. In the event of the manager, cleanup was not occurring
5856 *      and the class remained even after the modal was gone.
5857 * Solution:
5858 *      A param was added to the close dialog JS method that allowed proper cleanup of classes (.modal-open) added
5859 *      to the manager.
5860 *      https://github.com/SparkDevNetwork/Rock/commit/26cf101ae8e8923008ae42f3d833cab5a35df8cb
5861 *
5862 * July 1, 2020
5863 * Problem:
5864 *      Open a dialog. Close the dialog with a postback that calls C# Hide with no manager param. Open the dialog again
5865 *      and there is no backdrop.
5866 * Solution:
5867 *      This seems to be an issue with bootstrap (seems unlikely) or the combination of bootstrap with the postback
5868 *      UI re-rendering. The modal is removed from the DOM before our JS close method is reached. Therefore proper
5869 *      cleanup of the first modal open is never done. This seems to impact the second opening and bootstrap fails
5870 *      to add the backdrop. Thus our code adds a backdrop if one is not visible.
5871 *      https://github.com/SparkDevNetwork/Rock/commit/f08bb6fc1e17b79d0772152707acd4a3876a68d6
5872 *
5873 *  July 7, 2020
5874 *  Problem:
5875 *      Open a dialog. Close the dialog with a postback that calls C# Hide with no manager param. Open the dialog again
5876 *      and there is a backdrop but no modal.
5877 *  Solution:
5878 *      This is because the postback calls the C# Hide method, which does not provide the manager to the JS close modal
5879 *      method. Because of this, proper cleanup doesn't occur. We can calculate the manger as we did when we opened the
5880 *      modal, which fixes the issue.
5881 *      Also, there was a bug in hiding the backdrop where if the owner no longer existed
5882 *      in the DOM, then the backdrop was not removed.
5883 */
5884
5885(function ($) {
5886    'use strict';
5887    window.Rock = window.Rock || {};
5888    Rock.controls = Rock.controls || {};
5889
5890    Rock.controls.modal = (function () {
5891        // shows the IFrame #modal-popup modal
5892        var _showModalPopup = function (sender, popupUrl) {
5893            var $modalPopup = $('#modal-popup');
5894            var $modalPopupIFrame = $modalPopup.find('iframe');
5895
5896            // Use the anchor tag's title attribute as the title of the dialog box
5897            if (sender && sender.attr('title') != undefined) {
5898                $('#modal-popup_panel h3').html(sender.attr('title') + ' <small></small>');
5899            }
5900
5901            $modalPopupIFrame.height('auto');
5902
5903            $modalPopupIFrame.one('load', function () {
5904
5905                // now that the iframe is loaded, show it, set it's initial height and do a modal layout
5906                $('#modal-popup').fadeTo(0, 1);
5907
5908                var newHeight = $(this.contentWindow.document).height();
5909                if ($(this).height() != newHeight) {
5910                    $(this).height(newHeight);
5911                }
5912
5913                $('body').addClass('modal-open');
5914                if (document.body.clientHeight > window.innerHeight) {
5915                    $('body').css('padding-right', Rock.controls.util.getScrollbarWidth());
5916                }
5917                $('#modal-popup').modal('layout');
5918            });
5919
5920            // Use the anchor tag's href attribute as the source for the iframe
5921            // this will trigger the load event (above) which will show the popup
5922            $('#modal-popup').fadeTo(0, 0);
5923
5924            // Indicate to Rock that this content is being served in an iFrame modal.
5925            var separator = popupUrl.indexOf("?") === -1 ? "?" : "&";
5926            popupUrl = popupUrl + separator + "IsIFrameModal=true";
5927
5928            $modalPopupIFrame.attr('src', popupUrl);
5929            $('#modal-popup').modal({
5930                show: true,
5931                backdrop: 'static',
5932                keyboard: false,
5933                attentionAnimation: '',
5934                modalOverflow: true
5935            });
5936        }
5937
5938        // Closes the non-iframe modal dialog control
5939        var _closeModalControl = function ($modalDialog, $manager) {
5940            if ($modalDialog && $modalDialog.length && $modalDialog.modal) {
5941                $modalDialog.modal('hide');
5942            }
5943
5944            // remove the modal-open class from this modal's manager
5945            if ($manager && $manager.length) {
5946                $manager.each(function () {
5947                    $(this).removeClass('modal-open');
5948                });
5949            }
5950
5951            // if all modals are closed, remove the modal-open class from the body
5952            if ($('.modal:visible').length === 0) {
5953                $('body').removeClass('modal-open').css('padding-right', '');
5954            }
5955
5956            // Ensure any modalBackdrops are removed if its owner is no longer visible. Note that some
5957            // backdrops do not have an owner
5958            $('.modal-backdrop').each(function () {
5959                var $modalBackdrop = $(this);
5960                var ownerId = $modalBackdrop.data('modalId');
5961                var $owner = $('#' + ownerId);
5962                var isOwnerInDom = $owner.length > 0;
5963
5964                if (ownerId && (!isOwnerInDom || !$owner.is(':visible'))) {
5965                    $modalBackdrop.remove();
5966                }
5967            });
5968        };
5969
5970        // shows a non-IFrame modal dialog control
5971        var _showModalControl = function ($modalDialog, managerId, clickBackdropToClose, $hfModalVisible) {
5972            $('body').addClass('modal-open');
5973            if (document.body.clientHeight > window.innerHeight) {
5974                $('body').css('padding-right', Rock.controls.util.getScrollbarWidth());
5975            }
5976
5977            $modalDialog.modal({
5978                show: true,
5979                manager: managerId,
5980                backdrop: 'static',
5981                keyboard: false,
5982                attentionAnimation: '',
5983                modalOverflow: true,
5984                replace: true
5985            });
5986
5987            if ($('.modal-backdrop').filter(':visible').length === 0) {
5988                // ensure that there is a modal-backdrop and include its owner as an attribute so that we can remove it when this modal is closed
5989                $('<div class="modal-backdrop" data-modal-id="' + $modalDialog.prop('id') + '" />').appendTo('body');
5990            }
5991
5992            if (clickBackdropToClose) {
5993                $(".modal-scrollable").click(function (event) {
5994                    if (event.target === event.currentTarget) {
5995                        $(".modal-scrollable").off("click");
5996                        _closeModalControl($modalDialog, $(managerId));
5997
5998                        if ($hfModalVisible && $hfModalVisible.length) {
5999                            $hfModalVisible.val(0);
6000                        }
6001                    }
6002                });
6003            }
6004        }
6005
6006        var exports = {
6007            // updates the side of the modal that the control is in.
6008            // this function works for both the IFrame modal and ModalDialog control
6009            updateSize: function (controlId) {
6010                var $control = typeof (controlId) == 'string' ? $('#' + controlId) : $(controlId);
6011                if ($control && $control.length) {
6012                    var $modalBody = $control.closest('.modal-body');
6013                    if ($modalBody.is(':visible')) {
6014                        $modalBody[0].style.minHeight = "0";
6015                        var scrollHeight = $modalBody.prop('scrollHeight');
6016                        if ($modalBody.outerHeight() != scrollHeight) {
6017                            // if modalbody didn't already grow to fit (maybe because of a bootstrap dropdown) make modal-body big enough to fit.
6018                            $modalBody[0].style.minHeight = scrollHeight + "px";
6019
6020                            // force the resizeDetector to fire
6021                            if ($('#dialog').length && $('#dialog')[0].resizedAttached) {
6022                                $('#dialog')[0].resizedAttached.call();
6023                            }
6024                        }
6025                    }
6026                }
6027
6028            },
6029            // closes the #modal-popup modal (IFrame Modal)
6030            close: function (msg) {
6031                // do a setTimeout so this fires after the postback
6032                $('#modal-popup').hide();
6033                setTimeout(function () {
6034                    $('#modal-popup iframe').attr('src', '');
6035                    $('#modal-popup').modal('hide');
6036
6037                }, 0);
6038
6039                $('body').removeClass('modal-open').css('padding-right', '');
6040
6041                if (msg && msg != '') {
6042
6043                    if (msg == 'PAGE_UPDATED') {
6044                        location.reload(true);
6045                    }
6046                    else {
6047                        $('#rock-config-trigger-data').val(msg);
6048                        $('#rock-config-trigger').trigger('click');
6049                    }
6050                }
6051            },
6052            // closes a ModalDialog control (non-IFrame Modal)
6053            closeModalDialog: function ($modalDialog, $manager) {
6054                _closeModalControl($modalDialog, $manager);
6055            },
6056            // shows the #modal-popup modal (IFrame Modal)
6057            show: function (sender, popupUrl, detailsId, postbackUrl) {
6058                _showModalPopup(sender, popupUrl);
6059            },
6060            // shows a ModalDialog control (non-IFrame Modal)
6061            showModalDialog: function ($modalDialog, managerId, clickBackdropToClose, $hfModalVisible) {
6062                _showModalControl($modalDialog, managerId, clickBackdropToClose, $hfModalVisible);
6063            },
6064
6065            // gets the IFrame element of the global the Modal Popup (for the IFrame Modal)
6066            getModalPopupIFrame: function () {
6067                var $modalPopup = $('#modal-popup');
6068                var $modalPopupIFrame = $modalPopup.find('iframe');
6069
6070                return $modalPopupIFrame;
6071            }
6072        };
6073
6074        return exports;
6075    }());
6076}(jQuery));
6077;
6078(function ($) {
6079    'use strict';
6080    window.Rock = window.Rock || {};
6081    Rock.controls = Rock.controls || {};
6082
6083    /** JS helper for the NoteEditor control */
6084    Rock.controls.noteEditor = (function () {
6085        var exports = {
6086            /** initializes the JavasSript for the noteEditor control */
6087            initialize: function (options) {
6088
6089                if (!options.id) {
6090                    throw 'id is required';
6091                }
6092
6093                var self = this;
6094
6095                var $control = $('#' + options.id);
6096
6097                if ($control.length == 0) {
6098                    return;
6099                }
6100
6101                self.$noteEditor = $control;
6102
6103                this.initializeEventHandlers();
6104
6105                if (options.isEditing) {
6106                    var $noteContainer = self.$noteEditor.closest('.js-notecontainer');
6107
6108                    if (options.currentNoteId) {
6109                        // editing an existing note
6110                        var $displayedNote = $noteContainer.find("[data-note-id='" + options.currentNoteId + "']");
6111
6112                        // move note editor and display in place of the readonly version of note
6113                        self.$noteEditor.detach();
6114                        $displayedNote.parent('.js-note').prepend(self.$noteEditor);
6115                        self.$noteEditor.fadeIn();
6116
6117                        $displayedNote.hide();
6118                    }
6119                    else if (options.parentNoteId) {
6120                        // new reply to a note
6121                        var $replyToNote = $noteContainer.find("[data-note-id='" + options.parentNoteId + "']");
6122
6123                        // move note editor as a child note of the note we are replying to
6124                        self.$noteEditor.detach();
6125                        $replyToNote.append(self.$noteEditor)
6126                        self.$noteEditor.slideDown().find('textarea').trigger("focus");
6127                    }
6128                    else {
6129                        // new note, so just show it 
6130                        self.$noteEditor.fadeIn();
6131                    }
6132                }
6133            },
6134
6135            /**  */
6136            initializeEventHandlers: function () {
6137                // Initialize NoteEditor and NoteContainer events
6138                var self = this;
6139                var $noteContainer = self.$noteEditor.closest('.js-notecontainer');
6140
6141                $('.js-addnote,.js-editnote,.js-replynote', $noteContainer).on('click', function (e) {
6142                    var addNote = $(this).hasClass('js-addnote');
6143                    var editNote = $(this).hasClass('js-editnote');
6144                    var replyNote = $(this).hasClass('js-replynote');
6145                    var cancelNote = $(this).hasClass('js-editnote-cancel');
6146                    var deleteNote = $(this).hasClass('js-removenote');
6147
6148                    var sortDirection = $noteContainer.data('sortdirection');
6149                    var $noteEditor = $noteContainer.find('.js-note-editor');
6150                    var $currentNote = $(false);
6151                    $noteEditor.detach();
6152
6153                    // clear out any previously entered stuff
6154                    $noteEditor.find('.js-parentnoteid').val('');
6155                    $noteEditor.find('textarea').val('');
6156                    $noteEditor.find('input:checkbox').prop('checked', false);
6157                    $noteEditor.find('.js-notesecurity').hide();
6158
6159                    var $noteprivateInput = $noteEditor.find('.js-noteprivate');
6160                    $noteprivateInput.parent().show();
6161
6162                    if (addNote) {
6163                        e.preventDefault();
6164                        e.stopImmediatePropagation();
6165                        var postbackJs = $noteContainer.find(".js-add-postback").attr('href');
6166                        window.location = postbackJs;
6167                        return;
6168                    }
6169                    else {
6170                        $currentNote = $(this).closest('.js-noteviewitem');
6171                        var currentNoteId = $currentNote.data('note-id');
6172
6173                        if (replyNote) {
6174                            $noteContainer.find('.js-currentnoteid').val(currentNoteId);
6175                            $noteEditor.find('.js-parentnoteid').val(currentNoteId);
6176
6177                            e.preventDefault();
6178                            e.stopImmediatePropagation();
6179                            var postbackJs = $noteContainer.find(".js-reply-to-postback").attr('href');
6180                            window.location = postbackJs;
6181                            return;
6182                        }
6183                        else if (editNote) {
6184                            $noteContainer.find('.js-currentnoteid').val(currentNoteId);
6185
6186                            e.preventDefault();
6187                            e.stopImmediatePropagation();
6188                            var postbackJs = $noteContainer.find(".js-edit-postback").attr('href');
6189                            window.location = postbackJs;
6190                            return;
6191                        }
6192                    }
6193                });
6194
6195                $('.js-notesecurity', $noteContainer).on('click', function (e) {
6196                    var $securityBtn = $(this);
6197                    var entityTypeId = $securityBtn.data('entitytype-id');
6198                    var title = $securityBtn.data('title');
6199                    var currentNoteId = $securityBtn.data('entity-id');
6200                    var securityUrl = Rock.settings.get('baseUrl') + "Secure/" + entityTypeId + "/" + currentNoteId + "?t=" + title + "&pb=&sb=Done";
6201                    Rock.controls.modal.show($securityBtn, securityUrl);
6202                });
6203
6204                $('.js-editnote-cancel', $noteContainer).on('click', function (e) {
6205                    var $noteEditor = $noteContainer.find('.js-note-editor');
6206                    $noteEditor.slideUp();
6207
6208                    // show any notedetails that might have been hidden when doing the editing
6209                    $noteEditor.parent().find('.js-noteviewitem').slideDown();
6210                });
6211
6212                $('.js-removenote', $noteContainer).on('click', function (e) {
6213                    var $currentNote = $(this).closest('.js-noteviewitem');
6214                    var currentNoteId = $currentNote.attr('data-note-id');
6215                    $noteContainer.find('.js-currentnoteid').val(currentNoteId);
6216
6217                    e.preventDefault();
6218                    e.stopImmediatePropagation();
6219                    var postbackJs = $noteContainer.find(".js-delete-postback").attr('href');
6220                    return Rock.dialogs.confirm('Are you sure you want to delete this note?', function (result) {
6221                        if (result) {
6222                            window.location = postbackJs;
6223                        }
6224                    });
6225                });
6226
6227                $('.js-expandreply', $noteContainer).on('click', function (e) {
6228                    var $currentNote = $(this).closest('.js-note');
6229                    var $childNotesContainer = $currentNote.find('.js-childnotes').first();
6230                    $childNotesContainer.slideToggle(function (x) {
6231
6232                        // get a list of noteIds that have their child items visible, so that we can maintain that expansion after a postback
6233                        var expandedNoteIds = $(this).closest('.js-notec
6233ontainer').find('.js-noteviewitem:visible').map(function () {
6234                            var $noteItem = $(this).closest('.js-note');
6235                            var $childNotesExpanded = $noteItem.find('.js-childnotes:first').is(':visible');
6236                            if ($childNotesExpanded) {
6237                                return $(this).attr('data-note-id');
6238                            }
6239                            else {
6240                                return null;
6241                            }
6242                        }).get().join();
6243
6244                        var $expandedNoteIdsHiddenField = $noteContainer.find('.js-expandednoteids');
6245                        $expandedNoteIdsHiddenField.val(expandedNoteIds);
6246                    });
6247                });
6248            }
6249        }
6250
6251        return exports
6252    }());
6253}(jQuery));
6254;
6255(function ($) {
6256    'use strict';
6257    window.Rock = window.Rock || {};
6258    Rock.controls = Rock.controls || {};
6259
6260    Rock.controls.numberBox = (function () {
6261        var exports = {
6262            clientValidate: function (validator, args) {
6263                var validationControl = $(validator);
6264                var numberType = validationControl.attr('type');
6265
6266                // Only validate numeric types - currency and other formats require server validation.
6267                if (!(numberType === 'double' || numberType === 'integer')) {
6268                    return;
6269                }
6270
6271                var $numberBox = $(validator).closest('.js-number-box');
6272                var isValid = true;
6273                var validationMessage = '';
6274
6275                var label = $numberBox.find('label').text();
6276                if (!label) {
6277                    label = "Value";
6278                }
6279
6280                var value = args.Value;
6281
6282                // Check for a valid number - allow a signed or unsigned decimal with or without digit separators.
6283                var floatRegex = /(?=.*?\d)^[-,+]?(([1-9]\d{0,2}(,\d{3})*)|\d+)?(\.\d{1,9})?$/s;
6284                validationMessage = label + " must be a valid number.";
6285                isValid = floatRegex.test(value);
6286
6287                // Get the numeric value.
6288                value = parseFloat(value);
6289
6290                // Check for integer.
6291                if (isValid) {
6292                    if (numberType === "integer") {
6293                        validationMessage = label + " must be an integer value.";
6294                        isValid = Number.isInteger(value);
6295                    }
6296                }
6297
6298                // Check range.
6299                if (isValid) {
6300                    var max = parseFloat(validationControl.attr("max"));
6301                    var min = parseFloat(validationControl.attr("min"));
6302                    var checkMaxValue = !Number.isNaN( max );
6303                    var checkMinValue = !Number.isNaN( min );
6304
6305                    if ( checkMinValue && checkMaxValue) {
6306                        validationMessage = label + ' must have a value between ' + min + ' and ' + max + '.';
6307                        isValid = (value >= min && value <= max);
6308                    }
6309                    else if (checkMinValue) {
6310                        validationMessage = label + ' must have a value of ' + min + ' or more.';
6311                        isValid = (value >= min);
6312                    }
6313                    else if (checkMaxValue) {
6314                        validationMessage = label + ' must have a value of ' + max + ' or less.';
6315                        isValid = (value <= max);
6316                    }
6317                }
6318
6319                args.IsValid = isValid;
6320
6321                // Set the visual feedback.
6322                if (isValid) {
6323                    $numberBox.removeClass('has-error');
6324                    $numberBox.attr('title', '');
6325                }
6326                else {
6327                    $numberBox.addClass('has-error');
6328                    $numberBox.attr('title', validationMessage);
6329
6330                    validator.errormessage = validationMessage;
6331                    validator.isvalid = false;
6332                }
6333            }
6334        };
6335
6336        return exports;
6337    }());
6338}(jQuery));
6339;
6340(function ($) {
6341    'use strict';
6342    window.Rock = window.Rock || {};
6343    Rock.controls = Rock.controls || {};
6344
6345    Rock.controls.numberRangeEditor = (function () {
6346        var exports = {
6347            clientValidate: function (validator, args) {
6348                var $numberRangeEditor = $(validator).closest('.js-numberrangeeditor');
6349                var lowerValue = $numberRangeEditor.find('input.js-number-range-lower ').val();
6350                var upperValue = $numberRangeEditor.find('input.js-number-range-upper').val();
6351                var required = $numberRangeEditor.attr('data-required') == 'true';
6352                var itemLabelText = $numberRangeEditor.attr('data-itemlabel');
6353
6354                var isValid = true;
6355
6356                if (required) {
6357                    // if required, then make sure that the number range has a lower and/or upper value (can't both be blank)
6358                    if (lowerValue.length === 0 && upperValue.length === 0) {
6359                        isValid = false;
6360                        validator.errormessage = itemLabelText + ' is required';
6361                    }
6362                }
6363
6364                var control = $numberRangeEditor;
6365                var labelForSelector = "label[for='" + $numberRangeEditor.prop('id') + "']";
6366                var $labelFor = $(labelForSelector)
6367                if (isValid) {
6368                    control.removeClass('has-error');
6369                    $labelFor.parent().removeClass('has-error');
6370                }
6371                else {
6372                    control.addClass('has-error');
6373                    $labelFor.parent().addClass('has-error');
6374                }
6375
6376                args.IsValid = isValid;
6377            }
6378        };
6379
6380        return exports;
6381    }());
6382}(jQuery));
6383;
6384(function ($) {
6385    'use strict';
6386    window.Rock = window.Rock || {};
6387    Rock.controls = Rock.controls || {};
6388
6389    Rock.controls.numberUpDown = (function () {
6390
6391        var exports = {
6392            adjust: function (btn, adjustment, postbackScript) {
6393
6394                var $parent = $(btn).closest('div.numberincrement');
6395                var $min = $parent.find('.js-number-up-down-min').first();
6396                var $max = $parent.find('.js-number-up-down-max').first();
6397                var $value = $parent.find('.js-number-up-down-value').first();
6398                var $lbl = $parent.find('.js-number-up-down-lbl').first();
6399                var $upBtn = $parent.find('.js-number-up').first();
6400                var $downBtn = $parent.find('.js-number-down').first();
6401
6402                // Get the min, max, and new value
6403                var minValue = parseInt($min.val(), 10);
6404                var maxValue = parseInt($max.val(), 10);
6405                var numValue = parseInt($value.val(), 10) + adjustment;
6406
6407                // If new value is valid, set the hf and lbl
6408                if (numValue >= minValue && numValue <= maxValue) {
6409                    $value.val(numValue);
6410                    $lbl.html(numValue);
6411                }
6412
6413                // enable/disable the 'up' button
6414                if (numValue >= maxValue) {
6415                    $upBtn.addClass('disabled');
6416                } else {
6417                    $upBtn.removeClass('disabled');
6418                }
6419
6420                // enable/disable the 'down' button
6421                if (numValue <= minValue) {
6422                    $downBtn.addClass('disabled');
6423                } else {
6424                    $downBtn.removeClass('disabled');
6425                }
6426
6427                if (postbackScript && postbackScript != "" ) {
6428                    window.location = "javascript:" + postbackScript;
6429                }
6430
6431            }
6432        };
6433
6434        return exports;
6435    }());
6436}(jQuery));;
6437(function ($)
6438{
6439  'use strict';
6440  window.Rock = window.Rock || {};
6441  Rock.controls = Rock.controls || {};
6442
6443  Rock.controls.numberUpDownGroup = (function ()
6444  {
6445    var exports = {
6446      clientValidate: function (validator, args)
6447      {
6448        var $numberUpDownGroup = $(validator).closest('.js-number-up-down-group');
6449        var isValid = true;
6450
6451        if ($numberUpDownGroup.hasClass('required') === true) {
6452          isValid = false;
6453
6454          $numberUpDownGroup.find('.js-number-up-down-lbl').each(function (i) {
6455            if (parseInt(this.innerText, 10) > 0) {
6456              isValid = true;
6457            }
6458          });
6459        }
6460
6461        if (isValid === false) {
6462          validator.errormessage = $numberUpDownGroup.find('label').text() + " is required";
6463        }
6464
6465        var control = $numberUpDownGroup;
6466        if (isValid)
6467        {
6468          control.removeClass('has-error');
6469        }
6470        else
6471        {
6472          control.addClass('has-error');
6473        }
6474
6475        args.IsValid = isValid;
6476      }
6477    };
6478
6479    return exports;
6480  }());
6481}(jQuery));
6482;
6483(function ($) {
6484    'use strict';
6485    window.Rock = window.Rock || {};
6486    Rock.controls = Rock.controls || {};
6487
6488    Rock.controls.pbx = (function () {
6489
6490        var exports = {
6491            // implements the click to call feature
6492            originate: function (sourcePersonGuid, destinationNumber, callerId, destinationPersonName, destinationNumberFormatted) {
6493                var originateUrl = Rock.settings.get('baseUrl') + 'api/Pbx/Originate?sourcePersonGuid=' + sourcePersonGuid + '&destinationPhone=' + destinationNumber + '&callerId=' + callerId;
6494
6495                console.log('From Rock.controls.originateCall: ' + originateUrl);
6496                $.get(originateUrl, function (r) {
6497                    if (!r.Success) {
6498                        Rock.dialogs.alert("An error occurred while attempting to place call.  " + r.Message);
6499                    }
6500                });
6501            }
6502        };
6503
6504        return exports;
6505    }());
6506}(jQuery));;
6507(function ($) {
6508    'use strict';
6509    window.Rock = window.Rock || {};
6510    Rock.controls = Rock.controls || {};
6511
6512    Rock.controls.personPicker = (function () {
6513        var PersonPicker = function (options) {
6514            this.controlId = options.controlId;
6515            this.restUrl = options.restUrl;
6516            this.restDetailUrl = options.restDetailUrl;
6517            this.defaultText = options.defaultText || '';
6518            this.iScroll = null;
6519            this.$pickerControl = $('#' + this.controlId);
6520            this.$pickerScrollContainer = this.$pickerControl.find('.js-personpicker-scroll-container');
6521        };
6522
6523        PersonPicker.prototype.initializeEventHandlers = function () {
6524            var controlId = this.controlId,
6525                restUrl = this.restUrl,
6526                restDetailUrl = this.restDetailUrl || (Rock.settings.get('baseUrl') + 'api/People/GetSearchDetails'),
6527                defaultText = this.defaultText;
6528
6529            var $pickerControl = this.$pickerControl;
6530            var $searchFields = $pickerControl.find('.js-personpicker-search-field input');
6531            var $searchFieldName = $pickerControl.find('.js-personpicker-search-name input');
6532            var $searchFieldAddress = $pickerControl.find('.js-personpicker-search-address input');
6533            var $searchFieldPhone = $pickerControl.find('.js-personpicker-search-phone input');
6534            var $searchFieldEmail = $pickerControl.find('.js-personpicker-search-email input');
6535            var $searchResults = $pickerControl.find('.js-personpicker-searchresults');
6536            var $pickerToggle = $pickerControl.find('.js-personpicker-toggle');
6537            var $pickerMenu = $pickerControl.find('.js-personpicker-menu');
6538            var $pickerSelect = $pickerControl.find('.js-personpicker-select');
6539            var $pickerSelectNone = $pickerControl.find('.js-picker-select-none');
6540            var $pickerPersonId = $pickerControl.find('.js-person-id');
6541            var $pickerPersonName = $pickerControl.find('.js-person-name');
6542            var $pickerCancel = $pickerControl.find('.js-personpicker-cancel');
6543            var $pickerToggleAdditionalSearchFields = $pickerControl.find('.js-toggle-additional-search-fields');
6544            var $pickerAdditionalSearchFields = $pickerControl.find('.js-personpicker-additional-search-fields');
6545            var $pickerExpandSearchFields = $pickerControl.find('.js-expand-search-fields');
6546
6547            var includeBusinesses = $pickerControl.find('.js-include-businesses').val() == '1' ? 'true' : 'false';
6548            var includeDeceased = $pickerControl.find('.js-include-deceased').val() == '1' ? 'true' : 'false';
6549            var includeDetails = 'true';
6550
6551            var promise = null;
6552            var lastSelectedPersonId = null;
6553
6554            var autoCompletes = $searchFields.autocomplete({
6555                source: function (request, response) {
6556
6557                    var search = {
6558                        name: $searchFieldName.val(),
6559                        address: $searchFieldAddress.val(),
6560                        phone: $searchFieldPhone.val(),
6561                        email: $searchFieldEmail.val()
6562                    };
6563
6564                    // make sure that at least one of the search f
6564ields has 3 chars in it
6565                    if ((search.name.length < 3) && (search.address.length < 3) && (search.phone.length < 3) && (search.email.length < 3)) {
6566                        return;
6567                    }
6568
6569                    // abort any searches that haven't returned yet, so that we don't get a pile of results in random order
6570                    if (promise && promise.state() === 'pending') {
6571                        promise.abort();
6572                    }
6573
6574                    var searchParams = [];
6575                    if (search.name) {
6576                        searchParams.push("name=" + encodeURIComponent(search.name));
6577                    }
6578
6579                    // also search additional search fields if they are visible
6580                    if (search.address && $searchFieldAddress.is(':visible')) {
6581                        searchParams.push("address=" + encodeURIComponent(search.address));
6582                    }
6583
6584                    if (search.phone && $searchFieldPhone.is(':visible')) {
6585                        searchParams.push("phone=" + encodeURIComponent(search.phone));
6586                    }
6587
6588                    if (search.email && $searchFieldEmail.is(':visible')) {
6589                        searchParams.push("email=" + encodeURIComponent(search.email));
6590                    }
6591
6592                    var searchQuery = "?" + searchParams.join("&");
6593
6594                    // set the timeout to 20 seconds, just in case it takes a long time to search
6595                    promise = $.ajax({
6596                        url: restUrl
6597                            + searchQuery
6598                            + "&includeDetails=" + includeDetails
6599                            + "&includeBusinesses=" + includeBusinesses
6600                            + "&includeDeceased=" + includeDeceased,
6601                        timeout: 20000,
6602                        dataType: 'json'
6603                    });
6604
6605                    // Display a wait indicator to show that the search is now running.
6606                    if ($('.js-searching-notification').length == 0) {
6607                        $searchResults.prepend('<i class="ti ti-refresh ti-spin margin-l-md js-searching-notification" style="display: none; opacity: .4;"></i>');
6608                    }
6609                    $('.js-searching-notification').fadeIn(800);
6610
6611                    promise.done(function (data) {
6612                        $searchResults.html('');
6613                        response($.map(data, function (item) {
6614                            return item;
6615                        }));
6616
6617                        exports.personPickers[controlId].updateScrollbar();
6618                    });
6619
6620                    promise.fail(function (xhr, status, error) {
6621                        console.log(status + ' [' + error + ']: ' + xhr.responseText);
6622                        var errorCode = xhr.status;
6623                        if (errorCode == 401) {
6624                            $searchResults.html("<li class='text-danger'>Sorry, you're not authorized to search.</li>");
6625                        }
6626
6627                        $('.js-searching-notification').remove();
6628                    });
6629                },
6630                // Set minLength to 0, but check that at least one field has 3 chars before fetching from REST.
6631                // To minimize load on the server, don't trigger the search until a reasonable delay after the last keypress.
6632                minLength: 0,
6633                delay: 750,
6634                html: true,
6635                appendTo: $searchResults,
6636                pickerControlId: controlId,
6637                messages: {
6638                    noResults: function () { },
6639                    results: function () { }
6640                }
6641            });
6642
6643            var autoCompleteCustomRenderItem = function ($ul, item) {
6644                if (this.options.html) {
6645                    // override jQueryUI autocomplete's _renderItem so that we can do HTML for the ListItems
6646                    // derived from http://github.com/scottgonzalez/jquery-ui-extensions
6647
6648                    var inactiveWarning = "";
6649
6650                    if (!item.IsActive && item.RecordStatus) {
6651                        inactiveWarning = " <small>(" + item.RecordStatus + ")</small>";
6652                    }
6653                    if (item.IsDeceased) {
6654                        inactiveWarning = " <small class=\"text-danger\">(Deceased)</small>";
6655                    }
6656
6657                    var quickSummaryInfo = "";
6658                    if (item.FormattedAge || item.SpouseNickName) {
6659                        quickSummaryInfo = " <small class='rollover-item text-muted'>";
6660                        if (item.FormattedAge) {
6661                            quickSummaryInfo += "Age: " + item.FormattedAge;
6662                        }
6663
6664                        if (item.SpouseNickName) {
6665                            if (item.FormattedAge) {
6666                                quickSummaryInfo += "; ";
6667                            }
6668
6669                            quickSummaryInfo += "Spouse: " + item.SpouseNickName;
6670                        }
6671
6672                        quickSummaryInfo += "</small>";
6673                    }
6674
6675                    var $div = $('<div/>').attr('class', 'radio'),
6676
6677                        $label = $('<label/>')
6678                            .html('<span class="label-text">' + item.Name + inactiveWarning + quickSummaryInfo + '</span><i class="ti ti-refresh ti-spin margin-l-md loading-notification" style="display: none; opacity: .4;"></i>')
6679                            .prependTo($div),
6680
6681                        $radio = $('<input type="radio" name="person-id" />')
6682                            .attr('id', item.Id)
6683                            .attr('value', item.Id)
6684                            .prependTo($label),
6685
6686                        $li = $('<li/>')
6687                            .addClass('picker-select-item js-picker-select-item')
6688                            .attr('data-person-id', item.Id)
6689                            .attr('data-person-name', item.Name)
6690                            .html($div),
6691
6692                        $resultSection = $(this.options.appendTo);
6693
6694                    var $itemDetailsDiv = $('<div/>')
6695                        .addClass('picker-select-item-details js-picker-select-item-details clearfix');
6696
6697                    if (item.SearchDetailsHtml) {
6698                        $itemDetailsDiv.attr('data-has-details', true).html(item.SearchDetailsHtml);
6699                    }
6700                    else {
6701                        $itemDetailsDiv.attr('data-has-details', false);
6702                    }
6703
6704                    if (includeDetails === 'false') {
6705                        $itemDetailsDiv.hide();
6706                    }
6707
6708                    $itemDetailsDiv.appendTo($li);
6709
6710                    if (!item.IsActive) {
6711                        $li.addClass('is-inactive');
6712                    }
6713
6714                    return $resultSection.append($li);
6715                }
6716                else {
6717                    return $('<li></li>')
6718                        .data('item.autocomplete', item)
6719                        .append($('<a></a>').text(item.label))
6720                        .appendTo($ul);
6721                }
6722            }
6723
6724            // each search field has its own autocomplete object, so we'll need override with our custom _renderItem to each
6725            $.each(autoCompletes, function (a, b, c) {
6726                var autoComplete = $(autoCompletes[a]).data('ui-autocomplete');
6727
6728                // Debugging: override close to prevent it canceling when loosing focus when debugging
6729                // autoComplete.close = function () { };
6730
6731                autoComplete._renderItem = autoCompleteCustomRenderItem;
6732            });
6733
6734            $pickerToggle.on('click', function (e) {
6735                e.preventDefault();
6736                $(this).toggleClass("active");
6737                $pickerMenu.toggle(0, function () {
6738                    exports.personPickers[controlId].updateScrollbar();
6739                    $searchFieldName.trigger('focus');
6740                });
6741            });
6742
6743            $pickerControl.on('click', '.js-picker-select-item', function (e) {
6744                if (e.type == 'click' && $(e.target).is(':input') == false) {
6745                    // only process the click event if it has bubbled up to the input tag
6746                    return;
6747                }
6748
6749                e.stopPropagation();
6750
6751                var $selectedItem = $(this).closest('.js-picker-select-item');
6752                var $itemDetails = $selectedItem.find('.js-picker-select-item-details');
6753
6754                var selectedPersonId = $selectedItem.attr('data-person-id');
6755
6756                if ($itemDetails.is(':visible')) {
6757
6758                    if (selectedPersonId == lastSelectedPersonId && e.type == 'click') {
6759                        // if they are clicking the same person twice in a row (and the details are done expanding), assume that's the one they want to pick
6760                        var selectedText = $selectedItem.attr('data-person-name');
6761
6762                        setSelectedPerson(selectedPersonId, selectedText);
6763
6764                        $pickerSelect.trigger('onclick');
6765                        // Fire the postBack for the Select button.
6766                        var postBackUrl = $pickerSelect.prop('href');
6767                        if (postBackUrl) {
6768                            window.location = postBackUrl;
6769                        }
6770                    } else {
6771
6772                        // if it is already visible but isn't the same one twice, just leave it open
6773                    }
6774                }
6775
6776                if (includeDetails === 'false') {
6777                    // hide other open details
6778                    $('.js-picker-select-item-details', $pickerControl).filter(':visible').each(function () {
6779                        var $el = $(this),
6780                            currentPersonId = $el.closest('.js-picker-select-item').attr('data-person-id');
6781
6782                        if (currentPersonId != selectedPersonId) {
6783                            $el.slideUp();
6784                            exports.personPickers[controlId].updateScrollbar();
6785                        }
6786                    });
6787                }
6788
6789                lastSelectedPersonId = selectedPersonId;
6790
6791                if ($itemDetails.attr('data-has-details') == 'false') {
6792                    // add a spinner in case we have to wait on the server for a little bit
6793                    var $spinner = $selectedItem.find('.loading-notification');
6794                    $spinner.fadeIn(800);
6795
6796                    // fetch the search details from the server
6797                    $.get(restDetailUrl + '?Id=' + selectedPersonId, function (responseText, textStatus, jqXHR) {
6798                        $itemDetails.attr('data-has-details', true);
6799
6800                        // hide then set the HTML so that we can get the slideDown effect
6801                        $itemDetails.stop().hide().html(responseText);
6802                        showItemDetails($itemDetails);
6803
6804                        $spinner.stop().fadeOut(200);
6805                    });
6806                } else {
6807                    showItemDetails($selectedItem.find('.picker-select-item-details:hidden'));
6808                }
6809            });
6810
6811            var showItemDetails = function ($itemDetails) {
6812                if ($itemDetails.length) {
6813                    $itemDetails.slideDown(function () {
6814                        exports.personPickers[controlId].updateScrollbar();
6815                    });
6816                }
6817            }
6818
6819            $pickerControl.on('mouseenter',
6820                function () {
6821                    // only show the X if there is something picked
6822                    if (($pickerPersonId.val() || '0') !== '0') {
6823                        $pickerSelectNone.addClass('show-hover');
6824                    }
6825                });
6826
6827            $pickerCancel.on('click', function () {
6828
6829                clearSearchFields();
6830                $pickerMenu.slideUp(function () {
6831                    exports.personPickers[controlId].updateScrollbar();
6832                });
6833            });
6834
6835            $pickerSelectNone.on('click', function (e) {
6836                // prevent the click from bubbling up to the pickerControl click event
6837                e.preventDefault();
6838                e.stopPropagation();
6839
6840                var selectedValue = '0',
6841                    selectedText = defaultText;
6842
6843                $pickerPersonId.val(selectedValue);
6844                $pickerPersonName.val(selectedText);
6845                // run onclick event from the button
6846                $(this).trigger('onclick');
6847            });
6848
6849            // disable the enter key : this will prevent the enter key from clearing the loaded search query.
6850            $pickerControl.on('keypress', function (e) {
6851                if (e.which == 13) {
6852                    return false;
6853                }
6854            });
6855
6856            var setSelectedPerson = function (selectedValue, selectedText) {
6857                var selectedPersonLabel = $pickerControl.find('.js-personpicker-selectedperson-label');
6858
6859                $pickerPersonId.val(selectedValue);
6860                $pickerPersonName.val(selectedText);
6861
6862                selectedPersonLabel.val(selectedValue);
6863                selectedPersonLabel.text(selectedText);
6864
6865                $pickerMenu.slideUp();
6866            }
6867
6868            var clearSearchFields = function () {
6869                $searchFieldName.val('');
6870                $searchFieldAddress.val('');
6871                $searchFieldPhone.val('');
6872                $searchFieldEmail.val('');
6873            }
6874
6875            $pickerSelect.on('click', function () {
6876                var $radInput = $pickerControl.find('input:checked'),
6877                    selectedValue = $radInput.val(),
6878                    selectedText = $radInput.closest('.js-picker-select-item').attr('data-person-name');
6879
6880                setSelectedPerson(selectedValue, selectedText);
6881                clearSearchFields();
6882            });
6883
6884            var toggleSearchFields = function () {
6885                var expanded = $pickerExpandSearchFields.val();
6886                if (expanded == 1) {
6887                    $pickerAdditionalSearchFields.slideDown();
6888                }
6889                else {
6890                    $pickerAdditionalSearchFields.slideUp();
6891                }
6892
6893                $pickerToggleAdditionalSearchFields.toggleClass('active', expanded == 1);
6894            };
6895
6896            toggleSearchFields();
6897
6898            $pickerToggleAdditionalSearchFields.on('click', function () {
6899                var expanded = $pickerExpandSearchFields.val();
6900                if (expanded == 1) {
6901                    expanded = 0;
6902                }
6903                else {
6904                    expanded = 1;
6905                }
6906
6907                $pickerExpandSearchFields.val(expanded);
6908
6909                toggleSearchFields();
6910            });
6911
6912            $('.js-select-self', $pickerControl).on('click', function () {
6913                var selectedValue = $('.js-self-person-id', $pickerControl).val(),
6914                    selectedText = $('.js-self-person-name', $pickerControl).val();
6915
6916                setSelectedPerson(selectedValue, selectedText);
6917
6918                // fire the postBack of the btnSelect if there is one
6919                $pickerSelect.trigger('onclick');
6920                var postBackUrl = $pickerSelect.prop('href');
6921                if (postBackUrl) {
6922                    window.location = postBackUrl;
6923                }
6924            });
6925        };
6926
6927        PersonPicker.prototype.updateScrollbar = function () {
6928            var self = this;
6929
6930            // first, update this control's scrollbar, then the modal'
6930s
6931
6932            if (self.$pickerScrollContainer.is(':visible')) {
6933                if (self.iScroll) {
6934                    self.iScroll.refresh();
6935                }
6936            }
6937
6938            // update the outer modal scrollbar
6939            Rock.dialogs.updateModalScrollBar(this.controlId);
6940        }
6941
6942        PersonPicker.prototype.initialize = function () {
6943
6944            this.iScroll = new IScroll($('.viewport', this.$pickerControl)[0], {
6945                mouseWheel: true,
6946                indicators: {
6947                    el: $('.track', this.$pickerScrollContainer)[0],
6948                    interactive: true,
6949                    resize: false,
6950                    listenY: true,
6951                    listenX: false,
6952                },
6953                click: false,
6954                preventDefaultException: { tagName: /.*/ }
6955            });
6956
6957            this.initializeEventHandlers();
6958        };
6959
6960        var exports = {
6961            personPickers: {},
6962            findControl: function (controlId) {
6963                return exports.personPickers[controlId];
6964            },
6965            initialize: function (options) {
6966                if (!options.controlId) throw '`controlId` is required.';
6967                if (!options.restUrl) throw '`restUrl` is required.';
6968
6969                var personPicker = new PersonPicker(options);
6970                exports.personPickers[options.controlId] = personPicker;
6971                personPicker.initialize();
6972            }
6973        };
6974        return exports;
6975    }());
6976}(jQuery));
6977;
6978(function($) {
6979    'use strict';
6980    window.Rock = window.Rock || {};
6981    Rock.controls = Rock.controls || {};
6982
6983    Rock.controls.priorityNav = (function() {
6984        var RESIZE_DURATION = 500;
6985        var TAB_KEYCODE = 9;
6986        var ClassName = {
6987            PRIORITY: 'priority',
6988            HIDE: 'sr-only',
6989            RESIZING: 'resizing'
6990        };
6991        var Selector = {
6992            NAV_ELEMENTS: "li:not('.overflow-nav')",
6993            FIRST_ELEMENT: 'li:first',
6994            PRIORITY_ELEMENT: '.priority'
6995        };
6996        var MenuLabelDefault = 'More';
6997        var MenuLabelAllHiddenDefault = 'Menu';
6998        var MenuLabelCarat = '<i class="ml-1 ti ti-chevron-down" />'
6999        var MenuTemplate = function(MenuLabel) {
7000            return '<li class="overflow-nav dropdown d-none"><a href="#" class="dropdown-toggle nav-link overflow-nav-link" data-toggle="dropdown" role="button" aria-haspopup="true">' + MenuLabel + '</a><ul class="overflow-nav-list dropdown-menu dropdown-menu-right"></ul></li>';
7001        };
7002        var PriorityNav = function (options) {
7003            var element = $('.nav-tabs');
7004
7005            if (options && options.controlId) {
7006                element = $('#' + options.controlId);
7007            } else {
7008                options = '';
7009            }
7010
7011            this._element = element;
7012            this._config = options;
7013
7014            if ($(element).is('ul')) {
7015                this._$menu = $(element);
7016            } else {
7017                this._$menu = $(element)
7018                    .find('ul')
7019                    .first();
7020            }
7021            this._initMenu();
7022            this._$allNavElements = this._$menu.find(Selector.NAV_ELEMENTS);
7023            this._bindUIActions();
7024            this._setupMenu();
7025        };
7026
7027        PriorityNav.prototype = {
7028            constructor: PriorityNav,
7029            initialize: function() {},
7030            _initMenu: function() {
7031                var MenuLabel = this._config.MenuLabel
7032
7033                if (typeof MenuLabel === 'undefined') {
7034                  MenuLabel = MenuLabelDefault
7035                }
7036
7037                // add menu template
7038                this._$menu.append(MenuTemplate(MenuLabel));
7039            },
7040
7041            _setupMenu: function() {
7042                var $allNavElements = this._$allNavElements;
7043                // Checking position of the menu
7044                var menuPosition = this._$menu.position();
7045                // Get position of right
7046                var menuRight = menuPosition.left + this._$menu.outerWidth();
7047                // Checking top position of first item (sometimes changes)
7048                var firstPos = this._$menu.find(Selector.FIRST_ELEMENT).position();
7049
7050                // Empty collection in which to put menu items to move
7051                var $wrappedElements = $();
7052
7053                // Used to snag the previous menu item in addition to ones that have wrapped
7054                var first = true;
7055
7056                // Loop through all the nav items...
7057                this._$allNavElements.each(function(i) {
7058                    var $elm = $(this);
7059
7060                    // ...in which to find wrapped elements
7061                    var pos = $elm.position();
7062                    // Get position of right
7063                    var right = pos.left + $elm.outerWidth();
7064
7065                    if (pos.top !== firstPos.top || right > menuRight) {
7066                        // If element is wrapped, add it to set
7067                        $wrappedElements = $wrappedElements.add($elm);
7068
7069                        // Add the previous one too, if first
7070                        if (first) {
7071                            $wrappedElements = $wrappedElements.add($allNavElements.eq(i - 1));
7072                            first = false;
7073                        }
7074                    }
7075                });
7076
7077                if ($wrappedElements.length) {
7078                    // Clone set before altering
7079                    var newSet = $wrappedElements.clone();
7080
7081                    // Hide ones that we're moving
7082                    $wrappedElements.addClass(ClassName.HIDE);
7083                    $wrappedElements.find('a').attr('tabindex', -1);
7084
7085                    // Add wrapped elements to dropdown
7086                    this._$menu.find('.overflow-nav-list').append(newSet);
7087
7088                    // Show new menu
7089                    this._$menu.find('.overflow-nav').removeClass('d-none');
7090
7091                    // Check if menu doesn't overflow after process
7092                    if (this._$menu.find('.overflow-nav').position().top !== firstPos.top) {
7093                        var $item = $(this._element)
7094                            .find('.' + ClassName.HIDE)
7095                            .first()
7096                            .prev();
7097                        var $itemDuplicate = $item.clone();
7098
7099                        $item.addClass(ClassName.HIDE);
7100                        $item.find('a').attr('tabindex', -1);
7101
7102                        this._$menu.find('.overflow-nav-list').prepend($itemDuplicate);
7103                    }
7104
7105                    if ($allNavElements.length == $wrappedElements.length) {
7106                        this._$menu.find('.overflow-nav-link').html(MenuLabelAllHiddenDefault + ' ' + MenuLabelCarat);
7107                        this._$menu.find('.overflow-nav-list').removeClass('dropdown-menu-right');
7108                    } else {
7109                        this._$menu.find('.overflow-nav-link').html(MenuLabelDefault + ' ' + MenuLabelCarat);
7110                        this._$menu.find('.overflow-nav-list').addClass('dropdown-menu-right');
7111                    }
7112                }
7113
7114                // hide menu from AT
7115                this._$menu.find('.overflow-nav').attr('aria-hidden', true);
7116            },
7117
7118            _tearDown: function() {
7119                this._$menu.find('.overflow-nav-list').empty();
7120                this._$menu.find('.overflow-nav').addClass('d-none');
7121                this._$allNavElements.removeClass(ClassName.HIDE);
7122                this._$allNavElements.find('a').attr('tabindex', 0);
7123            },
7124
7125            _bindUIActions: function() {
7126                var self = this;
7127                $(window).on('resize', function() {
7128                    self._$menu.addClass(ClassName.RESIZING);
7129
7130                    setTimeout( function() {
7131                        self._tearDown();
7132                        self._setupMenu();
7133                        self._$menu.removeClass(ClassName.RESIZING);
7134                    }, RESIZE_DURATION);
7135                });
7136
7137                this._$menu.find('.overflow-nav .dropdown-toggle').on('keyup', function(e) {
7138                    if (e.which === TAB_KEYCODE) {
7139                        $(e.target).dropdown('toggle');
7140                    }
7141                });
7142            }
7143        }
7144
7145        var exports = {
7146            defaults: {
7147                controlId: null,
7148                name: 'prioritynav'
7149            },
7150            controls: {},
7151            initialize: function (options) {
7152                var settings = $.extend({}, exports.defaults, options);
7153
7154                if (!settings.controlId) throw 'controlId is required';
7155
7156                var priorityNav = new PriorityNav(settings);
7157
7158                // Delay initialization until after the DOM is ready
7159                $(function () {
7160                    priorityNav.initialize();
7161                });
7162            }
7163        };
7164
7165        return exports;
7166    })();
7167})(jQuery);
7168;
7169(function ($) {
7170    'use strict';
7171    window.Rock = window.Rock || {};
7172    Rock.controls = Rock.controls || {};
7173
7174    Rock.controls.rangeSlider = (function () {
7175        var exports = {
7176            initialize: function (options) {
7177                if (!options.controlId) {
7178                    throw 'controlId is required';
7179                }
7180
7181                Sys.Application.add_load(function () {
7182                    var cssFile = Rock.settings.get('baseUrl') + 'Scripts/ion.rangeSlider/css/ion.rangeSlider.Rock.css';
7183
7184                    // ensure that css for rangeSlider is added to page
7185                    if (!$('#rangeSliderCss').length) {
7186                        $('head').append("<link id='rangeSliderCss' href='" + cssFile + "' type='text/css' rel='stylesheet' />");
7187                    }
7188                });
7189
7190                // use https://github.com/IonDen/ion.rangeSlider/ to make a slider
7191                // see https://github.com/IonDen/ion.rangeSlider/#settings for settings
7192                $('#' + options.controlId).ionRangeSlider({
7193                    type: options.type || 'single', // Choose slider t
7193ype, could be 'single' for one handle, or 'double' for two handles
7194                    min: options.min || 0,
7195                    max: options.max || 100,
7196                    step: options.step || 1,
7197                    from: options.from || null, // for 'single' the position of the slider. for 'double' the lower position of the selected range
7198                    to: options.to || null, // if 'double' the upper position of the selected range
7199                    disable: options.disable || false // if 'true', the control will render as read-only
7200                });
7201
7202            }
7203        };
7204
7205        return exports;
7206    }());
7207}(jQuery));
7208;
7209(function ($) {
7210    'use strict';
7211    window.Rock = window.Rock || {};
7212    Rock.controls = Rock.controls || {};
7213
7214    Rock.controls.rockCheckBoxList = (function () {
7215
7216        var exports = {
7217            clientValidate: function (validator, args) {
7218                var $checkBoxList = $(validator).closest('.js-rockcheckboxlist');
7219
7220                var checkboxes = $checkBoxList.find('input');
7221                var isValid = false;
7222                for (var i = 0; i < checkboxes.length; i++) {
7223                    if (checkboxes[i].checked) {
7224                        isValid = true;
7225                        break;
7226                    }
7227                }
7228
7229                if (isValid) {
7230                        $checkBoxList.removeClass('has-error');
7231                } else {
7232                        $checkBoxList.addClass('has-error');
7233                }
7234
7235                args.IsValid = isValid;
7236            }
7237        };
7238
7239        return exports;
7240    }());
7241}(jQuery));
7242
7243
7244;
7245(function (Sys) {
7246    'use strict';
7247    Sys.Application.add_load(function () {
7248
7249        var $chosenDropDowns = $('.chosen-select');
7250        if ($chosenDropDowns.length) {
7251            // IE fix for 'chosen' causing activeElement to be a plain generic object (not a real node) after postback
7252            if (document.activeElement && !document.activeElement.nodeType) {
7253                $('body').trigger('focus');
7254            }
7255
7256            $chosenDropDowns.chosen({
7257                width: '100%',
7258                allow_single_deselect: true,
7259                placeholder_text_multiple: ' ',
7260                placeholder_text_single: ' '
7261            });
7262
7263            $chosenDropDowns.on('chosen:showing_dropdown chosen:hiding_dropdown', function (evt, params) {
7264                // update the outer modal
7265                Rock.dialogs.updateModalScrollBar(this);
7266            });
7267
7268            var $chosenDropDownsAbsolute = $chosenDropDowns.filter('.chosen-select-absolute');
7269            if ($chosenDropDownsAbsolute.length) {
7270                $chosenDropDownsAbsolute.on('chosen:showing_dropdown', function (evt, params) {
7271                    $(this).next('.chosen-container').find('.chosen-drop').css('position', 'relative');
7272                });
7273                $chosenDropDownsAbsolute.on('chosen:hiding_dropdown', function (evt, params) {
7274                    $(this).next('.chosen-container').find('.chosen-drop').css('position', 'absolute');
7275                });
7276            }
7277        }
7278    });
7279}(Sys));
7280;
7281(function ($, Sys) {
7282    'use strict';
7283    window.Rock = window.Rock || {};
7284    Rock.controls = Rock.controls || {};
7285
7286    Rock.controls.scheduleBuilder = (function () {
7287        var exports,
7288            ScheduleBuilder = function (options) {
7289                this.id = options.id;
7290            };
7291
7292        ScheduleBuilder.prototype.initializeEventHandlers = function () {
7293
7294            var id = this.id,
7295                $modal = $('#' + id).closest('.rock-modal');
7296
7297
7298            $modal.find('.schedule-type').off('click').on('click',function () {
7299                var recurrenceState = $('input[class=schedule-type]:checked').data('schedule-type');
7300
7301                if (recurrenceState === 'schedule-onetime') {
7302                    $modal.find('.js-schedule-recurrence-panel').slideUp();
7303                } else {
7304                    $modal.find('.js-schedule-recurrence-panel').slideDown();
7305                }
7306            });
7307
7308            $modal.find('.recurrence-pattern-radio').off('click').on('click', function () {
7309
7310                var recurrencePattern = '.' + $('input[class=recurrence-pattern-radio]:checked').data('recurrence-pattern');
7311
7312                if ($modal.find(recurrencePattern)) {
7313
7314                    $modal.find('.recurrence-pattern-type').not(recurrencePattern).hide();
7315
7316                    if (recurrencePattern == ".recurrence-pattern-specific-date") {
7317                        /* don't show continue-until or exclusion dates for specific date mode (since it doesn't make sense, plus ical ends up deleting them) */
7318                        $modal.find('.js-continue-until').hide();
7319                        $modal.find('.js-exclusion-dates').hide();
7320                    }
7321                    else {
7322                        $modal.find('.js-continue-until').show();
7323                        $modal.find('.js-exclusion-dates').show();
7324                    }
7325
7326                    $(recurrencePattern).fadeIn();
7327                }
7328            });
7329
7330            /** Specific Dates Scripts**/
7331
7332            // show datepicker, ok, cancel so that new date can be added to the list
7333            $modal.find('.add-specific-date').off('click').on('click', function () {
7334                $(this).hide();
7335                $modal.find('.js-add-specific-date-group').show();
7336            });
7337
7338            // add new date to list when ok is clicked
7339            $modal.find('.add-specific-date-ok').off('click').on('click', function () {
7340
7341                // get date list from hidden field
7342                var dateListValues = $modal.find('.recurrence-pattern-type > input:hidden').val().split(',');
7343
7344                if (!dateListValues[0]) {
7345                    // if dateList is blank, initialize as a new empty array
7346                    dateListValues = [];
7347                }
7348
7349                // validate
7350
7351                // set colors back to default just in case previously marked invalid
7352                var $datepicker = $modal.find('.specific-date input');
7353                $datepicker.css('color', '');
7354
7355                // var checkDate = Date.parse($datepicker.val());
7356                if ($datepicker.val() == null || $datepicker.val() == "") {
7357                    // blank, don't color, just return
7358                    return;
7359                }
7360
7361                var locale = window.navigator.userLanguage || window.navigator.language;
7362                moment.locale(locale);
7363                var checkDate = moment($datepicker.val(), 'l');
7364
7365                if (!checkDate.isValid) {
7366                    // invalid date entered, color red and return
7367                    $datepicker.css('color', 'red');
7368                    return;
7369                }
7370
7371                var newDate = $datepicker.val();
7372
7373                // delete newDate from list in case it is already there
7374                var index = dateListValues.indexOf(newDate);
7375                if (index >= 0) {
7376                    dateListValues.splice(index, 1);
7377                }
7378
7379                // add new date to list
7380                dateListValues.push(newDate);
7381
7382                // save list back to hidden field
7383                $modal.find('.js-specific-datelist-values').val(dateListValues);
7384
7385                // rebuild the UL
7386                var dateList = $modal.find(".lstSpecificDates");
7387                dateList.children().remove();
7388                $.each(dateListValues, function (i, value) {
7389                    // add to ul
7390                    var newLi = "<li><span>" + value + "</span> <a href='#' style='display: none'> <i class='ti ti-x'></i></a></li>";
7391                    dateList.append(newLi);
7392                });
7393
7394                $modal.find('.js-add-specific-date-group').hide();
7395                $modal.find('.add-specific-date').show();
7396            });
7397
7398            // cancel out of adding a new date
7399            $modal.find('.add-specific-date-cancel').off('click').on('click', function () {
7400                $modal.find('.js-add-specific-date-group').hide();
7401                $modal.find('.add-specific-date').show();
7402            });
7403
7404            // fadeIn/fadeOut the X buttons to delete dates
7405            $modal.find('.lstSpecificDates').on('mouseenter',
7406                function () {
7407                    $(this).find('li a').stop(true, true).show();
7408                }).on('mouseleave',
7409                function () {
7410                    $(this).find('li a').stop(true, true).fadeOut(500);
7411                });
7412
7413            // delete specific date from list
7414            $modal.find('.lstSpecificDates').off('click').on('click', 'li a', function () {
7415                var selectedDate = $(this).siblings().text();
7416
7417                // get date list from hidden field
7418                var $hiddenField = $modal.find('.js-specific-datelist-values');
7419                var dateList = $hiddenField.val().split(',');
7420
7421                // delete selectedDate
7422                var index = dateList.indexOf(selectedDate);
7423                if (index >= 0) {
7424                    dateList.splice(index, 1);
7425                }
7426
7427                // save list back to hidden field
7428                $hiddenField.val(dateList);
7429
7430                // remove date from ul list
7431                var liItem = $(this).parent();
7432                liItem.remove();
7433            });
7434
7435            /** Exclusion DateRanges Scripts **/
7436
7437            // show dateRangepicker, ok, cancel so that new dateRange can be added to the list
7438            $modal.find('.add-exclusion-daterange').off('click').on('click', function () {
7439
7440                $(this).hide();
7441                $modal.find('.js-add-exclusion-daterange-group').show(function () {
7442                    Rock.controls.modal.updateSize();
7443                });
7444            });
7445
7446            // add new date to list when ok is clicked
7447            $modal.find('.add-exclusion-daterange-ok').off('click').on('click', function () {
7448
7449                // get daterange list from hidden field
7450                var dateRangeListValues = $modal.find('.js-exclusion-daterange-list-values').val().split(',');
7451                if (!dateRangeListValues[0]) {
7452                    // if blank, initialize as a new empty array
7453                    dateRangeListValues = [];
7454                }
7455
7456                var $exclusionDateRange = $modal.find('.js-exclusion-date-range-picker input');
7457                var newDateRange = $exclusionDateRange.first().val() + ' - ' + $exclusionDateRange.last().val();
7458
7459                // delete newDateRange from list in case it is already there
7460                var index = dateRangeListValues.indexOf(newDateRange);
7461                if (index >= 0) {
7462                    dateRangeListValues.splice(index, 1);
7463                }
7464
7465                // add new dateRange to list
7466                dateRangeListValues.push(newDateRange);
7467
7468                // save list back to hidden field
7469                $modal.find('.js-exclusion-daterange-list-values').val(dateRangeListValues);
7470
7471                // rebuild the UL
7472                var dateRangeList = $modal.find('.lstExclusionDateRanges');
7473                dateRangeList.children().remove();
7474                $.each(dateRangeListValues, function (i, value) {
7475                    // add to ul
7476                    var newLi = "<li><span>" + value + "</span><a href='#' style='display: none'><i class='ti ti-x'></i></a></li>";
7477                    dateRangeList.append(newLi);
7478                });
7479
7480                $modal.find('.js-add-exclusion-daterange-group').hide();
7481                $modal.find('.add-exclusion-daterange').show();
7482            });
7483
7484            // cancel out of adding a new dateRange
7485            $modal.find('.add-exclusion-daterange-cancel').off('click').on('click', function () {
7486                $modal.find('.js-add-exclusion-daterange-group').hide();
7487                $modal.find('.add-exclusion-daterange').show();
7488            });
7489
7490            // fadeIn/fadeOut the X buttons to delete dateRanges
7491            $modal.find('.lstExclusionDateRanges').on('mouseenter',
7492                function () {
7493                    $(this).find('li a').stop(true, true).show();
7494                }).on('mouseleave',
7495                function () {
7496                    $(this).find('li a').stop(true, true).fadeOut(500);
7497                });
7498
7499            // delete dateRange from list
7500            $modal.find('.lstExclusionDateRanges').off('click').on('click', 'li a', function () {
7501                var selectedDateRange = $(this).siblings('span').text();
7502
7503                // get dateRange list from hidden field
7504                var $hiddenField = $modal.find('.js-exclusion-daterange-list-values');
7505                var dateRangeList = $hiddenField.val().split(',');
7506
7507                // delete selectedDateRange
7508                var index = dateRangeList.indexOf(selectedDateRange);
7509                if (index >= 0) {
7510                    dateRangeList.splice(index, 1);
7511                }
7512
7513                // save list back to hidden field
7514                $hiddenField.val(dateRangeList);
7515
7516                // remove dateRange from ul list
7517                var liItem = $(this).parent();
7518                liItem.remove();
7519            });
7520
7521            // validate on Save.  Make sure they have at least a StartDate and Time set
7522            $modal.find('.js-modaldialog-save-link').off('click').on('click', function (event) {
7523
7524                var locale = window.navigator.userLanguage || window.navigator.language;
7525                moment.locale(locale);
7526
7527                var $datetimepicker = $modal.find('[id*="dpStartDateTime"]').find('input'),
7528                    startDateValue = moment($datetimepicker.first().val(), 'l'),
7529                    startTimeValue = $datetimepicker.last().val();
7530
7531                if (!startDateValue.isValid) {
7532                    $datetimepicker.parents('.form-group').first().toggleClass('has-error', 1);
7533                    event.preventDefault();
7534                    return;
7535                }
7536                else {
7537                    $datetimepicker.parents('.form-group').first().toggleClass('has-error', 0);
7538                }
7539            });
7540        };
7541
7542        exports = {
7543            cache: {},
7544            initialize: function (options) {
7545                if (!options.id) {
7546                    throw 'id is required';
7547                }
7548
7549                var sb = new ScheduleBuilder(options);
7550                exports.cache[options.id] = sb;
7551
7552                Sys.Application.add_load(function () {
7553                    sb.initializeEventHandlers();
7554                });
7555            }
7556        };
7557
7558        return exports;
7559    }());
7560}(jQuery, Sys));
7561;
7562(function ($) {
7563    'use strict';
7564    window.Rock = window.Rock || {};
7565    Rock.controls = Rock.controls || {};
7566
7567    Rock.controls.screenKeyboard = (function () {
7568        //
7569        // Handle toggling the Shift key states.
7570        //
7571        var toggleShift = function ($keyboard) {
7572            $keyboard.find('[data-command="shift"]').toggleClass('active');
7573
7574            setAltMode($keyboard, $keyboard.find('[data-command="shift"].active').length > 0);
7575        };
7576
7577        //
7578        // Handle setting digits to either normal value or alternate value.
7579        //
7580        var setAltMode = function ($keyboard, altMode) {
7581            $keyboard.find('.digit').each(function () {
7582                $(this).text(altMode === true ? $(this).data('alt-value') : $(this).data('value'));
7583            });
7584        };
7585
7586        //
7587        // Process normal digit presses.
7588        //
7589        var digitClick = function () {
7590            var $keyboard = $(this).closest('.screen-keyboard');
7591            var $target = $('#' + $keyboard.data('target'));
7592
7593            $target.val($target.val() + $(this).text());
7594            $target.trigger('input');
7595
7596            if ($keyboard.find('[data-command="shift"].active').length > 0) {
7597                toggleShift($keyboard);
7598            }
7599        };
7600
7601        //
7602        // Process special command keys.
7603        //
7604        var commandClick = function (options) {
7605            var $keyboard = $(this).closest('.screen-keyboard');
7606            var $target = $('#' + $keyboard.data('target'));
7607            var command = $(this).data('command');
7608
7609            if (command === 'backspace') {
7610                var val = $target.val();
7611                if (val.length > 0) {
7612                    val = val.substr(0, val.length - 1);
7613                }
7614                $target.val(val);
7615                $target.trigger('input');
7616            }
7617            else if (command === 'clear') {
7618                $target.val('');
7619                $target.trigger('input');
7620            }
7621            else if (command === 'shift') {
7622                $keyboard.find('[data-command="caps"]').removeClass('active');
7623                toggleShift($keyboard);
7624            }
7625            else if (command === 'caps') {
7626                $keyboard.find('[data-command="shift"]').removeClass('active');
7627                $(this).toggleClass('active');
7628
7629                setAltMode($keyboard, $(this).hasClass('active'));
7630            }
7631            else if (command === 'tab') {
7632                /* TODO: What do we do with this action? */
7633            }
7634            else if (command === 'enter') {
7635                window.location = "javascript:__doPostBack('" + options.postback + "', 'enter')";
7636            }
7637        };
7638
7639        var exports = {
7640            initialize: function (options) {
7641                if (!options.id) {
7642                    throw 'id is required';
7643                }
7644
7645                var $keyboard = $('#' + options.id);
7646
7647                $keyboard.find('.digit').on('click', digitClick);
7648                $keyboard.find('.command').on('click', function () { commandClick.apply(this, [options]); });
7649            }
7650        };
7651
7652        return exports;
7653    }());
7654}(jQuery));
7655;
7656(function () {
7657    'use strict';
7658    window.Rock = window.Rock || {};
7659    Rock.controls = Rock.controls || {};
7660
7661    Rock.controls.searchField = (function () {
7662        var exports,
7663            SearchField = function (options) {
7664                this.controlId = options.controlId;
7665                this.$el = $('#' + this.controlId);
7666                this.name = options.name;
7667            };
7668
7669        SearchField.prototype = {
7670            constructor: SearchField,
7671            initialize: function () {
7672                var self = this;
7673
7674                var key = sessionStorage.getItem("com.rockrms.search");
7675                if (key && key != '') {
7676                    var $search = self.$el.parents('.smartsearch');
7677                    $search.find('input:hidden').val(key);
7678                    $search.find('a.dropdown-toggle > span').html($search.find('li[data-key="' + key + '"] > a').html());
7679                }
7680
7681                this.$el.typeahead({
7682                    name: this.name,
7683                    limit: 15,
7684                    remote: {
7685                        url: Rock.settings.get('baseUrl') + 'api/search?type=%TYPE&term=%QUERY&$top=15',
7686                        replace: function (url, uriEncodedQuery) {
7687                            var query = url;
7688                            query = query.replace('%TYPE', self.$el.parents('.smartsearch').find('input:hidden').val());
7689                            query = query.replace('%QUERY', uriEncodedQuery);
7690                            return query;
7691                        }
7692                    }
7693                });
7694
7695                this.initializeEventHandlers();
7696            },
7697            initializeEventHandlers: function () {
7698                var self = this,
7699                    search = function (term) {
7700
7701                        // clear the selected item from the input as it might have HTML which w
7701ould show while the search page loads
7702                        var searchControl = document.getElementById(self.controlId);
7703                        searchControl.value = '';
7704
7705                        // search for data elements in the search term
7706                        var $dataEl = $("<p>" + term + "</p>").find("data").first();
7707
7708                        // see if this is a universal search by looking for return-type and return-id data params
7709                        var returnType = $dataEl.attr("return-type");
7710                        var returnId = $dataEl.attr("return-id");
7711
7712                        if (returnType == null || returnId == null) {
7713                            var otherParams = '';
7714
7715                            // take any data attributes and add them as query parameters to the url
7716                            $dataEl.each(function () {
7717                                $.each(this.attributes, function () {
7718                                    if (this) {
7719                                        otherParams += '&' + this.name + '=' + encodeURIComponent(this.value);
7720                                    }
7721                                });
7722                            });
7723
7724                            // remove any search accessories. These are html elements with the class .search-accessory
7725                            var resultElement = document.createElement('div');
7726                            resultElement.innerHTML = term;
7727                            //var resultElement = $("<div/>").html(term);
7728                            var accessories = resultElement.getElementsByClassName('search-accessory');
7729
7730                            // remove all accessory elements
7731                            while (accessories[0]) { 
7732                                accessories[0].parentNode.removeChild(accessories[0]);
7733                            }
7734
7735                            // remove any html from the search term before putting it in the url
7736                            var targetTerm = resultElement.textContent.trim();
7737
7738                            var keyVal = self.$el.parents('.smartsearch').find('input:hidden').val(),
7739                                $li = self.$el.parents('.smartsearch').find('li[data-key="' + keyVal + '"]'),
7740                                targetUrl = $li.attr('data-target'),
7741                                url = Rock.settings.get('baseUrl') + targetUrl.replace('{0}', encodeURIComponent(targetTerm));
7742
7743                            if (otherParams != '') {
7744                                if (url.indexOf('?') > -1) {
7745                                    url += otherParams
7746                                } else {
7747                                    url += otherParams.replace(/^&/, '?')
7748                                }
7749                            }
7750
7751                            window.location = url;
7752                        } else {
7753                            // universal search uses returnType and returnId, so build the url for that
7754                            var keyVal = self.$el.parents('.smartsearch').find('input:hidden').val(),
7755                                $li = self.$el.parents('.smartsearch').find('li[data-key="' + keyVal + '"]'),
7756                                targetUrl = $li.attr('data-target'),
7757                                url = Rock.settings.get('baseUrl') + targetUrl.replace('{0}', encodeURIComponent(returnType) + "/" + encodeURIComponent(returnId));
7758
7759                            window.location = url;
7760                        }
7761                    };
7762
7763                // Listen for typeahead's custom events and trigger search when hit
7764                this.$el.on('typeahead:selected typeahead:autocompleted', function (e, obj, name) {
7765                    search(obj.value);
7766                });
7767
7768                // Listen for the ENTER key being pressed while in the search box and trigger search when hit
7769                this.$el.on('keydown', function (e) {
7770                    if (e.keyCode === 13) {
7771                        e.preventDefault();
7772                        return false;
7773                    }
7774                });
7775                this.$el.on('keyup', function (e) {
7776                    if (e.keyCode === 13 && "" !== $(this).val().trim() ) {
7777                        search($(this).val());
7778                    }
7779                });
7780
7781                // Wire up "change" handler for search type "dropdown menu"
7782                this.$el.parents('.smartsearch').find('.dropdown-menu a').on('click', function () {
7783                    var $this = $(this),
7784                        text = $this.html();
7785
7786                    var key = $this.parent().attr('data-key');
7787                    sessionStorage.setItem("com.rockrms.search", key);
7788
7789                    $this.parents('.dropdown-menu').siblings('.navbar-link').find('span').html(text);
7790                    self.$el.parents('.smartsearch').find('input:hidden').val(key)
7791                });
7792            }
7793        };
7794
7795        exports = {
7796            defaults: {
7797                controlId: null,
7798                name: 'search'
7799            },
7800            controls: {},
7801            initialize: function (options) {
7802                var searchField,
7803                    settings = $.extend({}, exports.defaults, options);
7804
7805                if (!settings.controlId) throw 'controlId is required';
7806
7807                if (!exports.controls[settings.controlId]) {
7808                    searchField = new SearchField(settings);
7809                    exports.controls[settings.controlId] = searchField;
7810                } else {
7811                    searchField = exports.controls[settings.controlId];
7812                }
7813
7814                // Delay initialization until after the DOM is ready
7815                $(function () {
7816                    searchField.initialize();
7817                });
7818            }
7819        };
7820
7821        return exports;
7822    }());
7823}());
7824;
7825(function ($) {
7826    'use strict';
7827    window.Rock = window.Rock || {};
7828    Rock.controls = Rock.controls || {};
7829
7830    Rock.controls.slidingDateRangePicker = (function () {
7831        var exports = {
7832            initialize: function (options) {
7833                if (!options.id) {
7834                    throw 'id is required';
7835                }
7836
7837                var $picker = $('#' + options.id);
7838
7839                // the dropdown with current, last, previous, daterange in it
7840                var $select = $picker.find('.js-slidingdaterange-select');
7841
7842                showHideControls($picker, $select);
7843                updateDateRangeInfo($picker);
7844
7845                $select.on('change', function () {
7846                    showHideControls($picker, $select);
7847                    updateDateRangeInfo($picker);
7848                });
7849
7850                $('.js-number, .js-time-units-singular, .js-time-units-plural, .js-slidingdaterange-select, .js-lower, .js-upper', $picker).on('change', function () {
7851                    updateDateRangeInfo($picker);
7852                });
7853
7854                $('.js-number', $picker).on('keyup', function () {
7855                    updateDateRangeInfo($picker);
7856                });
7857            }
7858        },
7859        showHideControls = function ($picker, $select) {
7860            var selectedValue = $select.val();
7861            var isLast = selectedValue == '0';
7862            var isCurrent = selectedValue == '1';
7863            var isDateRange = selectedValue == '2';
7864            var isPrevious = selectedValue == '4';
7865            var isNext = selectedValue == '8';
7866            var isUpcoming = selectedValue == '16';
7867            $picker.find('.js-number').toggle(isLast || isPrevious || isNext || isUpcoming);
7868            $picker.find('.js-time-units-singular').toggle(isCurrent);
7869            $picker.find('.js-time-units-plural').toggle(isLast || isPrevious || isNext || isUpcoming);
7870            $picker.find('.js-time-units-date-range').toggle(isDateRange);
7871            var $pickerContainer = $picker.closest('.js-slidingdaterange-container');
7872            if (isLast || isPrevious || isNext || isUpcoming || isCurrent) {
7873                $pickerContainer.find('.js-slidingdaterange-info').css("display", "inline");
7874            } else {
7875                $pickerContainer.find('.js-slidingdaterange-info').hide();
7876            }
7877        },
7878        updateDateRangeInfo = function ($picker) {
7879            var timeUnitType = 0;
7880            if ($picker.find('.js-time-units-singular').is(':visible')) {
7881                timeUnitType = $picker.find('.js-time-units-singular').val();
7882            } else {
7883                timeUnitType = $picker.find('.js-time-units-plural').val();
7884            }
7885
7886            var numberOf = $picker.find('.js-number').val();
7887
7888            var $pickerContainer = $picker.closest('.js-slidingdaterange-container');
7889
7890            var dateRangeString = '';
7891            var $select = $picker.find('.js-slidingdaterange-select');
7892            var selectedValue = $select.val();
7893            if (selectedValue == '2') {
7894                var startPicker = $pickerContainer.find('.js-lower');
7895                var startDate = startPicker.find('.form-control').val();
7896
7897                var endPicker = $pickerContainer.find('.js-upper');
7898                var endDate = endPicker.find('.form-control').val();
7899
7900                if (startDate != '' && endDate != '') {
7901                    dateRangeString = '&startDate=' + startDate + '&endDate=' + endDate;
7902                }
7903            }
7904
7905            if (selectedValue != '2' || dateRangeString != '') {
7906                var getDateRangeUrl = Rock.settings.get('baseUrl') + 'api/Utility/CalculateSlidingDateRange?slidingDateRangeType=' + $select.val() + '&timeUnitType=' + timeUnitType + '&number=' + numberOf + dateRangeString;
7907                $.get(getDateRangeUrl, function (r) {
7908                    $pickerContainer.find('.js-slidingdaterange-info').text(r);
7909                });
7910
7911                var getTextValueUrl = Rock.settings.get('baseUrl') + 'api/Utility/GetSlidingDateRangeTextValue?slidingDateRangeType=' + $select.val() + '&timeUnitType=' + timeUnitType + '&number=' + numberOf + dateRangeString;
7912                $.get(getTextValueUrl, function (r) {
7913                    $pickerContainer.find('.js-slidingdaterange-text-value').val(r).change();
7914                });
7915            }
7916        };
7917
7918        return exports;
7919    }());
7920}(jQuery));
7921;
7922(function ($) {
7923    'use strict';
7924    window.Rock = window.Rock || {};
7925    Rock.controls = Rock.controls || {};
7926
7927    Rock.controls.tagList = (function () {
7928        var TagList = function (options) {
7929                this.controlId = options.controlId;
7930                this.entityTypeId = options.entityTypeId;
7931                this.currentPersonId = options.currentPersonId;
7932                this.entityGuid = options.entityGuid;
7933                this.entityQualifierColumn = options.entityQualifierColumn;
7934                this.entityQualifierValue = options.entityQualifierValue;
7935                this.preventTagCreation = options.preventTagCreation;
7936                this.delaySave = options.delaySave;
7937                this.categoryGuid = options.categoryGuid;
7938                this.includeInactive = options.includeInactive;
7939            },
7940            exports;
7941
7942        TagList.prototype.verifyTag = function (tagName) {
7943            // Since `verifyTag` is being called by the jQuery lib, `this`
7944            // is the current TagList DOM element. Get the HTML ID and fetch
7945            // from the cache.
7946            var tagList = exports.tagLists[$(this).attr('id')];
7947            if (! /^((?!<)(?!>)(?!%)(?!&).)*$/.test(tagName)) {
7948                Rock.dialogs.alert("Invalid characters have been entered for the tag name. Angle brackets, percent, and ampersand are not allowed.");
7949                $('#' + tagList.controlId).removeTag(tagName);
7950                return;
7951            }
7952
7953            var restUrl = Rock.settings.get('baseUrl') + 'api/tags';
7954            restUrl += '?entityTypeId=' + tagList.entityTypeId;
7955            restUrl += '&ownerId=' + tagList.currentPersonId;
7956            restUrl += '&name=' + encodeURIComponent(tagName);
7957            restUrl += '&includeInactive' + tagList.includeInactive.toString();
7958
7959            if (tagList.entityQualifierColumn) {
7960                restUrl += '&entityQualifier=' + tagList.entityQualifierColumn;
7961            }
7962
7963            if (tagList.entityQualifierValue) {
7964                restUrl += '&entityQualifierValue=' + tagList.entityQualifierValue;
7965            }
7966
7967            if (tagList.categoryGuid && tagList.categoryGuid != '') {
7968                restUrl += '&categoryGuid=' + tagList.categoryGuid;
7969            }
7970
7971            $.ajax({
7972                url: restUrl,
7973                statusCode: {
7974                    404: function () {
7975                        if (tagList.preventTagCreation) {
7976                            $('#' + tagList.controlId).removeTag(tagName);
7977                        }
7978                        else {
7979                            Rock.dialogs.confirm('A tag called "' + $('<div/>').text(tagName).html() + '" does not exist. Do you want to create a new personal tag?', function (result) {
7980                                if (result) {
7981                                    tagList.addTag(tagName);
7982                                } else {
7983                                    $('#' + tagList.controlId).removeTag(tagName);
7984                                }
7985                            });
7986                        }
7987                    },
7988                    403: function () {
7989                        Rock.dialogs.alert("Invalid characters have been entered for the tag name. Angle brackets, percent, and ampersand are not allowed.");
7990                        $('#' + tagList.controlId).removeTag(tagName);
7991                    },
7992                    200: function () {
7993                        tagList.addTag(tagName);
7994                    }
7995                }
7996            });
7997        };
7998
7999        TagList.prototype.addTag = function (tagName) {
8000            // `addTag` is invoked by `verifyTag` on an instance of a `TagList` object.
8001            // `this` is the current TagList instance in scope.
8002
8003            var tagList = this,
8004                restUrl = Rock.settings.get('baseUrl') + 'api/taggeditems'
8005            restUrl += '?entityTypeId=' + tagList.entityTypeId;
8006            restUrl += '&ownerId=' + tagList.currentPersonId;
8007            restUrl += '&entityGuid=' + tagList.entityGuid;
8008            restUrl += '&name=' + encodeURIComponent(tagName);
8009            restUrl += '&includeInactive' + tagList.includeInactive.toString();
8010
8011            if (tagList.categoryGuid && tagList.categoryGuid != '') {
8012                restUrl += '&categoryGuid=' + this.categoryGuid;
8013            }
8014
8015            // only attempt to add tag if an entityGuid exists
8016            if (!tagList.delaySave) {
8017                if (tagList.entityQualifierColumn) {
8018                    restUrl += '/' + tagList.entityQualifierColumn;
8019                }
8020
8021                if (tagList.entityQualifierValue) {
8022                    restUrl += '/' + tagList.entityQualifierValue;
8023                }
8024
8025                $.ajax({
8026                    type: 'POST',
8027                    url: restUrl,
8028                    error: function (xhr, status, error) {
8029                        console.log('AddTag() status: ' + status + ' [' + error + ']: ' + xhr.responseText);
8030                    }
8031                });
8032            }
8033        };
8034
8035        TagList.prototype.removeTag = function (tagName) {
8036
8037            // Since `removeTag` is being called by the jQuery lib, `this`
8038            // is the current TagList DOM element. Get the HTML ID and fetch
8039            // from the cache.
8040            var tagList = exports.tagLists[$(this).attr('id')],
8041                restUrl = Rock.settings.get('baseUrl') + 'api/taggeditems';
8042
8043            // only attempt to remove tag if an entityGuid exists
8044            if (!tagList.delaySave) {
8045
8046                restUrl += '?entityTypeId=' + tagList.entityTypeId;
8047                restUrl += '&ownerId=' + tagList.currentPersonId;
8048                restUrl += '&entityGuid=' + tagList.entityGuid;
8049                restUrl += '&name=' + encodeURIComponent(tagName);
8050                restUrl += '&includeInactive' + tagList.includeInactive.toString();
8051
8052                if (tagList.entityQualifierColumn) {
8053                    restUrl += '&entityQualifier=' + tagList.entityQualifierColumn;
8054                }
8055
8056                if (tagList.entityQualifierValue) {
8057                    restUrl += '&entityQualifierValue=' + tagList.entityQualifierValue;
8058                }
8059
8060                if (tagList.categoryGuid && tagList.categoryGuid != '') {
8061                    restUrl += '&categoryGuid=' + tagList.categoryGuid;
8062                }
8063
8064                $.ajax({
8065                    type: 'DELETE',
8066                    url: restUrl,
8067                    context: {
8068                        tagName: tagName,
8069                        tagsInput: this
8070                    },
8071                    error: function (xhr, status, error) {
8072                        if (xhr && xhr.status == 404) {
8073                            // already deleted
8074                            return;
8075                        }
8076
8077                        Rock.dialogs.alert('Unable to remove tag: ' + error);
8078
8079                        // put the tag back in (in alpha order, case-insensitive)
8080                        var tagsCommaList = $(this.tagsInput).val() + ',' + this.tagName
8081
8082                        tagsCommaList = tagsCommaList.split(',').sort(function (a, b) {
8083                            return a.toLowerCase().localeCompare(b.toLowerCase());
8084                        }).join(',');
8085
8086                        $(this.tagsInput).importTags(tagsCommaList);
8087                        return false;
8088                    }
8089                });
8090            }
8091
8092        };
8093
8094        TagList.prototype.initialize = function () {
8095
8096            var restUrl = Rock.settings.get('baseUrl') + 'api/Tags/AvailableNames';
8097            restUrl += '?entityTypeId=' + this.entityTypeId;
8098            restUrl += '&ownerId=' + this.currentPersonId;
8099            restUrl += '&entityGuid=' + this.entityGuid;
8100            restUrl += '&includeInactive' + this.includeInactive.toString();
8101
8102            if (this.entityQualifierColumn) {
8103                restUrl += '&entityQualifier=' + this.entityQualifierColumn;
8104            }
8105            if (this.entityQualifierValue) {
8106                restUrl += '&entityQualifierValue=' + this.entityQualifierValue;
8107            }
8108            if (this.categoryGuid && this.categoryGuid != '') {
8109                restUrl += '&categoryGuid=' + this.categoryGuid;
8110            }
8111
8112            $('ul.ui-autocomplete').css({ 'width': '300px' });
8113
8114            $('#' + this.controlId).tagsInput({
8115                autocomplete_url: function (request, response) {
8116                    $.ajax({
8117                        url: restUrl + '&name=' + request.term,
8118                        dataType: 'json',
8119                        success: function (data, status, xhr) {
8120                            response($.map(data, function (item) {
8121                                return {
8122                                    value: item.Name,
8123                                    class: !item.OwnerId ? 'system' : 'personal'
8124                                };
8125                            }));
8126                        },
8127                        error: function (xhr, status, error) {
8128                            console.log('availablenames status: ' + status + ' [' + error + ']: ' + xhr.reponseText);
8129                        }
8130                    });
8131                },
8132                autoCompleteAppendTo: 'div.tag-wrap',
8133                autoCompleteMessages: {
8134                    noResults: function () {},
8135                    results: function () {}
8136                },
8137                height: 'auto',
8138                width: '100%',
8139                interactive: true,
8140                defaultText: 'add tag',
8141                removeWithBackspace: false,
8142                onAddTag: this.verifyTag,
8143                onRemoveTag: this.removeTag
8144            });
8145        };
8146
8147        exports = {
8148            tagLists: {},
8149            initialize: function (options) {
8150                if (!options.controlId) throw 'controlId must be set.';
8151                if (!options.entityTypeId) throw 'entityTypeId must be set.';
8152                if (!options.currentPersonId) throw 'currentPersonId must be set';
8153                if (!options.entityGuid) throw 'entityGuid must be set';
8154
8155                var tagList = new TagList(options);
8156                tagList.options = options;
8157                exports.tagLists[options.controlId] = tagList;
8158                tagList.initialize();
8159            }
8160        };
8161
8162        return exports;
8163    }());
8164}(jQuery));
8165;
8166(function ($) {
8167    'use strict';
8168    window.Rock = window.Rock || {};
8169    Rock.controls = Rock.controls || {};
8170
8171    Rock.controls.taskActivityProgressReporter = (function () {
8172        const TaskActivityProgressReporter = function (options) {
8173            if (!Rock.RealTime) {
8174                throw new Error("realtime.js must be included first.");
8175            }
8176
8177            if (!options.controlId) {
8178                throw new Error("controlId is required.");
8179            }
8180
8181            this.controlId = options.controlId;
8182            this.connectionId = undefined;
8183
8184            this.connect();
8185        }
8186
8187        TaskActivityProgressReporter.prototype.getTaskId = function () {
8188            const control = document.getElementById(this.controlId);
8189
8190            if (!control) {
8191                return null;
8192            }
8193
8194            return control.getAttribute("data-task-id");
8195        };
8196
8197        TaskActivityProgressReporter.prototype.connect = async function () {
8198            this.topic = await Rock.RealTime.getTopic("Rock.RealTime.Topics.TaskActivityProgressTopic");
8199
8200            this.topic.on("taskStarted", this.onTaskStarted.bind(this));
8201            this.topic.on("taskCompleted", this.onTaskCompleted.bind(this));
8202            this.topic.on("updateTaskProgress", this.onUpdateTaskProgress.bind(this));
8203
8204            const $connectionId = $(`#${this.controlId} [id$='_hfConnectionId']`);
8205
8206            $connectionId.val(this.topic.connectionId || "");
8207        };
8208
8209        TaskActivityProgressReporter.prototype.onTaskStarted = function (status) {
8210            // Intentionally left blank for now.
8211        };
8212
8213        TaskActivityProgressReporter.prototype.onTaskCompleted = function (status) {
8214            const taskId = this.getTaskId();
8215
8216            if (status.taskId !== taskId) {
8217                return;
8218            }
8219
8220            const $preparing = $(`#${this.controlId} .js-preparing`);
8221            const $progress = $(`#${this.controlId} .js-progress-div`);
8222            const $results = $(`#${this.controlId} .js-results`);
8223
8224            $results.removeClass("alert-danger").removeClass("alert-warning").removeClass("alert-success");
8225
8226            if (status.errors && status.errors.length > 0) {
8227                console.error(status.errors);
8228                $results.addClass("alert-danger");
8229            }
8230            else if (status.warnings && status.warnings.length > 0) {
8231                console.warn(status.warnings);
8232                $results.addClass("alert-warning");
8233            }
8234            else {
8235                $results.addClass("alert-success");
8236            }
8237
8238            $results.text(status.message).slideDown();
8239            $preparing.slideUp();
8240            $progress.slideUp();
8241        };
8242
8243        TaskActivityProgressReporter.prototype.onUpdateTaskProgress = function (progress) {
8244            const taskId = this.getTaskId();
8245
8246            if (progress.taskId !== taskId) {
8247                return;
8248            }
8249
8250            const $preparing = $(`#${this.controlId} .js-preparing`);
8251            const $progress = $(`#${this.controlId} .js-progress-div`);
8252            const $bar = $(`#${this.controlId} .js-progress-bar`);
8253
8254            $bar.prop("aria-valuenow", progress.completionPercentage);
8255            $bar.prop("aria-valuemax", "100");
8256            $bar.css("width", `${progress.completionPercentage}%`);
8257
8258            if (progress.message) {
8259                $bar.text(progress.message);
8260            }
8261            else {
8262                $bar.text(`${progress.completionPercentage}%`);
8263            }
8264
8265            $progress.slideDown();
8266            $preparing.slideUp();
8267        };
8268
8269        const reporters = {};
8270
8271        const exports = {
8272            initialize(options) {
8273                if (!options.controlId) {
8274                    throw new Error("id is required.");
8275                }
8276
8277                if (reporters[options.controlId]) {
8278                    return;
8279                }
8280
8281                reporters[options.controlId] = new TaskActivityProgressReporter(options);
8282            }
8283        };
8284
8285        return exports;
8286    }());
8287}(jQuery));
8288;
8289(function ($) {
8290    'use strict';
8291    window.Rock = window.Rock || {};
8292    Rock.controls = Rock.controls || {};
8293
8294    Rock.controls.timePicker = (function () {
8295        var exports = {
8296            initialize: function (options) {
8297                if (!options.id) {
8298                    throw 'id is required';
8299                }
8300
8301                var $tp = $('#' + options.id);
8302
8303                // bootstrap-timepicker requires that the parent div have bootstrap-timepicker, input-append classes
8304                $tp.closest('div').addClass('bootstrap-timepicker').addClass('input-append');
8305
8306                // uses https://github.com/jdewit/bootstrap-timepicker
8307                $tp.timepicker({
8308                    defaultTime: false
8309                });
8310
8311                $tp.closest('.js-timepicker-input').find('.js-timepicker-clear').on('click', function () {
8312                    $tp.timepicker('clear');
8313                });
8314            }
8315        };
8316
8317        return exports;
8318    }());
8319}(jQuery));
8320;
8321(function ($) {
8322    'use strict';
8323    window.Rock = window.Rock || {};
8324    Rock.controls = Rock.controls || {};
8325
8326    Rock.controls.toggleButton = (function () {
8327        var exports = {
8328            initialize: function (options) {
8329                if (!options.id) {
8330                    throw 'id is required';
8331                }
8332
8333                // uses pattern from http://www.bootply.com/92189
8334
8335                $('#' + options.id + ' .btn-toggle').on('click', function (e) {
8336
8337                    e.stopImmediatePropagation();
8338
8339                    $(this).find('.btn').toggleClass('active');
8340
8341                    if (options.activeButtonCssClass && $(this).find('.' + options.activeButtonCssClass).length > 0) {
8342                        $(this).find('.btn').toggleClass(options.activeButtonCssClass);
8343                    }
8344
8345                    if (options.onButtonCssClass) {
8346                        $(this).find('.js-toggle-on').toggleClass(options.onButtonCssClass);
8347                    }
8348
8349                    if (options.offButtonCssClass) {
8350                        $(this).find('.js-toggle-off').toggleClass(options.offButtonCssClass);
8351                    }
8352
8353                    $(this).parent().find('.js-toggle-checked').val($(this).find('.js-toggle-on').hasClass('active'));
8354
8355                });
8356
8357            }
8358        };
8359
8360        return exports;
8361    }());
8362}(jQuery));
8363;
8364(function ($) {
8365    'use strict';
8366    window.Rock = window.Rock || {};
8367    Rock.controls = Rock.controls || {};
8368
8369    Rock.controls.universalItemSearchPicker = (function () {
8370        var UniversalItemSearchPicker = function (options) {
8371            this.controlId = options.controlId;
8372            this.restUrl = options.restUrl;
8373            this.context = options.context;
8374            this.areDetailsAlwaysVisible = options.areDetailsAlwaysVisible;
8375            this.iScroll = null;
8376            this.$pickerControl = $('#' + this.controlId);
8377            this.$pickerScrollContainer = this.$pickerControl.find('.js-universalitemsearchpicker-scroll-container');
8378        };
8379
8380        UniversalItemSearchPicker.prototype.initializeEventHandlers = function () {
8381            var controlId = this.controlId;
8382            var restUrl = this.restUrl;
8383            var context = this.context;
8384            var areDetailsAlwaysVisible = this.areDetailsAlwaysVisible;
8385
8386            var $pickerControl = this.$pickerControl;
8387            var $searchFields = $pickerControl.find('input.js-universalitemsearchpicker-search-field');
8388            var $searchFieldName = $pickerControl.find('input.js-universalitemsearchpicker-search-name');
8389            var $searchFieldIncludeInactive = $pickerControl.find("input.js-include-inactive");
8390            var $searchResults = $pickerControl.find('.js-universalitemsearchpicker-searchresults');
8391            var $pickerToggle = $pickerControl.find('.js-universalitemsearchpicker-toggle');
8392            var $pickerMenu = $pickerControl.find('.js-universalitemsearchpicker-menu');
8393            var $pickerSelect = $pickerControl.find('.js-universalitemsearchpicker-select');
8394            var $pickerSelectNone = $pickerControl.find('.js-picker-select-none');
8395            var $pickerItemValue = $pickerControl.find('.js-item-value');
8396            var $pickerItemName = $pickerControl.find('.js-item-name');
8397            var $pickerCancel = $pickerControl.find('.js-universalitemsearchpicker-cancel');
8398            var promise = null;
8399            var lastSelectedItemValue = null;
8400
8401            var autoCompletes = $searchFields.autocomplete({
8402                source: function (request, response) {
8403
8404                    var search = {
8405                        value: $searchFieldName.val()
8406                    };
8407
8408                    // make sure that at least one of the search fields has 3 chars in it
8409                    if (search.value.length < 3) {
8410                        return;
8411                    }
8412
8413                    // abort any searches that haven't returned yet, so that we don't get a pile of results in random order
8414                    if (promise && promise.state() === 'pending') {
8415                        promise.abort();
8416                    }
8417
8418                    var searchBag = {
8419                        context: context,
8420                        value: search.value,
8421                        isInactiveIncluded: $searchFieldIncludeInactive && $searchFieldIncludeInactive.is(":checked") == true
8422                    };
8423
8424                    // set the timeout to 20 seconds, just in case it takes a long time to search
8425                    promise = $.ajax({
8426                        url: restUrl,
8427                        method: "POST",
8428                        data: JSON.stringify(searchBag),
8429                        contentType: "application/json",
8430                        timeout: 20000,
8431                        dataType: 'json'
8432                    });
8433
8434                    // Display a wait indicator to show that the search is now running.
8435                    if ($('.js-searching-notification').length == 0) {
8436                        $searchResults.prepend('<i class="ti ti-refresh ti-spin margin-l-md js-searching-notification" style="display: none; opacity: .4;"></i>');
8437                    }
8438                    $('.js-searching-notification').fadeIn(800);
8439
8440                    promise.done(function (data) {
8441                        $searchResults.html('');
8442
8443                        response(data);
8444
8445                        exports.universalItemSearchPickers[controlId].updateScrollbar();
8446                    });
8447
8448                    promise.fail(function (xhr, status, error) {
8449                        console.log(status + ' [' + error + ']: ' + xhr.responseText);
8450                        var errorCode = xhr.status;
8451                        if (errorCode == 401) {
8452                            $searchResults.html("<li class='text-danger'>Sorry, you're not authorized to search.</li>");
8453                        }
8454
8455                        $('.js-searching-notification').remove();
8456                    });
8457                },
8458                // Set minLength to 0, but check that at least one field has 3 chars before fetching from REST.
8459                // To minimize load on the server, don't trigger the search until a reasonable delay after the last keypress.
8460                minLength: 0,
8461                delay: 750,
8462                html: true,
8463                appendTo: $searchResults,
8464                pickerControlId: controlId,
8465                messages: {
8466                    noResults: function () { },
8467                    results: function () { }
8468                }
8469            });
8470
8471            var autoCompleteCustomRenderItem = function ($ul, item) {
8472                // override jQueryUI autocomplete's _renderItem so that we can do HTML for the ListItems
8473                // derived from http://github.com/scottgonzalez/jquery-ui-extensions
8474
8475                var $div = $('<div/>').attr('class', 'radio');
8476
8477                var $labelText = $('<span/>')
8478                    .addClass('label-text d-flex gap')
8479                    .append($('<span/>').addClass('flex-grow-1').text(item.title));
8480
8481                if (item.labels) {
8482                    var $labels = $('<span/>').addClass('d-flex gap');
8483
8484                    for (let labelIndex = 0; labelIndex < item.labels.length; labelIndex++) {
8485                        $('<span/>')
8486                            .text(item.labels[labelIndex].text)
8487                            .addClass('label label-' + item.labels[labelIndex].value)
8488                            .appendTo($labels);
8489                    }
8490
8491                    $labelText.append($labels);
8492                }
8493
8494                var $label = $('<label/>')
8495                    .append($labelText)
8496                    .prependTo($div);
8497
8498                $('<input type="radio" name="item-id" />')
8499                    .attr('value', item.Id)
8500                    .prependTo($label);
8501
8502                var $li = $('<li/>')
8503                    .addClass('picker-select-item js-picker-select-item')
8504                    .css('list-style', 'none')
8505                    .attr('data-item-value', item.value)
8506                    .attr('data-item-name', item.title)
8507                    .html($div);
8508
8509                var $resultSection = $(this.options.appendTo);
8510
8511                var $itemDetailsDiv = $('<div/>')
8512                    .addClass('picker-select-item-details js-picker-select-item-details clearfix');
8513
8514                if (item.description) {
8515                    var $descriptionDiv = $('<div/>');
8516
8517                    $descriptionDiv.addClass("picker-select-item-description mb-2");
8518                    $descriptionDiv.text(item.description);
8519
8520                    $itemDetailsDiv.append($descriptionDiv);
8521                }
8522
8523                if (item.details) {
8524                    for (let detailIndex = 0; detailIndex < item.details.length; detailIndex++) {
8525                        var detail = item.details[detailIndex];
8526                        var $detailDl = $('<dl/>').addClass("d-flex");
8527                        $detailDl.append($("<dt/>").addClass("mr-2 text-nowrap").text(detail.value));
8528                        $detailDl.append($("<dd/>").text(detail.text));
8529
8530                        $itemDetailsDiv.append($detailDl);
8531                    }
8532                }
8533
8534                if (!areDetailsAlwaysVisible) {
8535                    $itemDetailsDiv.hide();
8536                }
8537
8538                $itemDetailsDiv.appendTo($li);
8539
8540                if (item.isInactive) {
8541                    $li.addClass('is-inactive');
8542                }
8543
8544                return $resultSection.append($li);
8545            }
8546
8547            // each search field has its own autocomplete object, so we'll need override with our custom _renderItem to each
8548            $.each(autoCompletes, function (a, b, c) {
8549                var autoComplete = $(autoCompletes[a]).data('ui-autocomplete');
8550
8551                // Debugging: override close to prevent it canceling when loosing focus when debugging
8552                // autoComplete.close = function () { };
8553
8554                autoComplete._renderItem = autoCompleteCustomRenderItem;
8555            });
8556
8557            $pickerToggle.on('click', function (e) {
8558                e.preventDefault();
8559                $(this).toggleClass("active");
8560                $pickerMenu.toggle(0, function () {
8561                    exports.universalItemSearchPickers[controlId].updateScrollbar();
8562                    $searchFieldName.trigger('focus');
8563                });
8564            });
8565
8566            $pickerControl.on('click', '.js-picker-select-item', function (e) {
8567                if (e.type == 'click' && $(e.target).is(':input') == false) {
8568                    // only process the click event if it has bubbled up to the input tag
8569                    return;
8570                }
8571
8572                e.stopPropagation();
8573
8574                var $selectedItem = $(this).closest('.js-picker-select-item');
8575                var $itemDetails = $selectedItem.find('.js-picker-select-item-details');
8576
8577                var selectedItemValue = $selectedItem.attr('data-item-value');
8578
8579                if ($itemDetails.is(':visible')) {
8580                    if (selectedItemValue == lastSelectedItemValue && e.type == 'click') {
8581                        // if they are clicking the same item twice in a row
8582                        // (and the details are done expanding), assume that's
8583                        // the one they want to pick
8584                        $pickerSelect.trigger('click');
8585                    } else {
8586                        // if it is already visible but isn't the same one twice, just leave it open
8587                    }
8588                }
8589
8590                lastSelectedItemValue = selectedItemValue;
8591
8592                showItemDetails($selectedItem.find('.picker-select-item-details:hidden'));
8593            });
8594
8595            var showItemDetails = function ($itemDetails) {
8596                if ($itemDetails.length) {
8597                    $itemDetails.slideDown(function () {
8598                        exports.universalItemSearchPickers[controlId].updateScrollbar();
8599                    });
8600                }
8601            }
8602
8603            $pickerControl.on('mouseenter',
8604                function () {
8605                    // only show the X if there is something picked
8606                    if (($pickerItemValue.val() || '') !== '') {
8607                        $pickerSelectNone.addClass('show-hover');
8608                    }
8609                });
8610
8611            $pickerCancel.on('click', function () {
8612                clearSearchFields();
8613                $pickerMenu.slideUp(function () {
8614                    exports.universalItemSearchPickers[controlId].updateScrollbar();
8615                });
8616            });
8617
8618            $pickerSelectNone.on('click', function (e) {
8619                // prevent the click from bubbling up to the pickerControl click event
8620                e.preventDefault();
8621                e.stopPropagation();
8622
8623                $pickerItemValue.val('');
8624                $pickerItemName.val('');
8625                // run onclick event from the button
8626                $(this).trigger('onclick');
8627            });
8628
8629            // disable the enter key : this will prevent the enter key from clearing the loaded search query.
8630            $pickerControl.on('keypress', function (e) {
8631                if (e.which == 13) {
8632                    return false;
8633                }
8634            });
8635
8636            var setSelectedItem = function (selectedValue, selectedText) {
8637                var selectedItemLabel = $pickerControl.find('.js-universalitemsearchpicker-selecteditem-label');
8638
8639                $pickerItemValue.val(selectedValue);
8640                $pickerItemName.val(selectedText);
8641
8642                selectedItemLabel.val(selectedValue);
8643                selectedItemLabel.text(selectedText);
8644
8645                $pickerMenu.slideUp();
8646            }
8647
8648            var clearSearchFields = function () {
8649                $searchFieldName.val('');
8650            }
8651
8652            $pickerSelect.on('click', function () {
8653                var $selectedItem = $pickerControl.find('input:checked').closest('.js-picker-select-item');
8654                var selectedItemValue = $selectedItem.attr('data-item-value');
8655                var selectedText = $selectedItem.attr('data-item-name');
8656
8657                setSelectedItem(selectedItemValue, selectedText);
8658                clearSearchFields();
8659
8660                // Fire the postBack for the Select button.
8661                var postBackUrl = $pickerSelect.prop('href');
8662                if (postBackUrl) {
8663                    window.location = postBackUrl;
8664                }
8665            });
8666        };
8667
8668        UniversalItemSearchPicker.prototype.updateScrollbar = function () {
8669            var self = this;
8670
8671            // first, update this control's scrollbar, then the modal's
8672
8673            if (self.$pickerScrollContainer.is(':visible')) {
8674                if (self.iScroll) {
8675                    self.iScroll.refresh();
8676                }
8677            }
8678
8679            // update the outer modal scrollbar
8680            Rock.dialogs.updateModalScrollBar(this.controlId);
8681        }
8682
8683        UniversalItemSearchPicker.prototype.initialize = function () {
8684            this.iScroll = new IScroll($('.viewport', this.$pickerControl)[0], {
8685                mouseWheel: true,
8686                indicators: {
8687                    el: $('.track', this.$pickerScrollContainer)[0],
8688                    interactive: true,
8689                    resize: false,
8690                    listenY: true,
8691                    listenX: false,
8692                },
8693                click: false,
8694                preventDefaultException: { tagName: /.*/ }
8695            });
8696
8697            this.initializeEventHandlers();
8698        };
8699
8700        var exports = {
8701            universalItemSearchPickers: {},
8702            findControl: function (controlId) {
8703                return exports.universalItemSearchPickers[controlId];
8704            },
8705            initialize: function (options) {
8706                if (!options.controlId) throw '`controlId` is required.';
8707                if (!options.restUrl) throw '`restUrl` is required.';
8708
8709                var picker = new UniversalItemSearchPicker(options);
8710                exports.universalItemSearchPickers[options.controlId] = picker;
8711                picker.initialize();
8712            }
8713        };
8714        return exports;
8715    }());
8716}(jQuery));
8717;
8718(function ($) {
8719    'use strict';
8720    window.Rock = window.Rock || {};
8721    Rock.controls = Rock.controls || {};
8722
8723    Rock.controls.util = (function () {
8724        var exports = {
8725            googleMapsIsLoaded: function () {
8726                // trigger googleMapsIsLoaded so that controls (like the geoPicker) know about it
8727                $(window).trigger('googleMapsIsLoaded');
8728            },
8729            loadGoogleMapsApi: function (apiSrc) {
8730                // ensure that js for googleMapsApi is added to page
8731                if (!$('#googleMapsApi').length) {
8732                    // by default, jquery adds a cache-busting parameter on dynamically added script tags. set the ajaxSetup cache:true to prevent this
8733                    $.ajaxSetup({ cache: true });
8734                    var src = apiSrc + '&callback=Rock.controls.util.googleMapsIsLoaded';
8735                    $('head').prepend("<script id='googleMapsApi' src='" + src + "' />");
8736                } else if (typeof google == 'object' && typeof google.maps == 'object') {
8737                    this.googleMapsIsLoaded();
8738                }
8739            },
8740          // parses the value as a float and adds a 'px' suffix if successful. If the value can't be parsed, this will return an empty string
8741          // note: this uses javascript's parseFloat which will convert the the numeric portion value to a float as long as it starts with a numeric value
8742            getValueAsPixels: function (a)
8743            {
8744              var floatValue = parseFloat(a);
8745              if (floatValue) {
8746                return floatValue + 'px';
8747              }
8748              else {
8749                return '';
8750              }
8751            },
8752            getScrollbarWidth: function () {
8753                // thx d.walsh
8754                var scrollDiv = document.createElement('div');
8755                scrollDiv.className = 'modal-scrollbar-measure';
8756                document.body.appendChild(scrollDiv);
8757                var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
8758                document.body.removeChild(scrollDiv);
8759                return scrollbarWidth;
8760            } // Static
8761        };
8762
8763        return exports;
8764
8765    }());
8766}(jQuery));
8767;
8768(function ($) {
8769    'use strict';
8770    window.Rock = window.Rock || {};
8771    Rock.controls = Rock.controls || {};
8772    const DefaultComparisonTypes = [
8773        { Value: 1, Text: 'Equal To' },
8774        { Value: 2, Text: 'Not Equal To' },
8775        { Value: 4, Text: 'Starts With' },
8776        { Value: 8, Text: 'Contains' },
8777        { Value: 16, Text: 'Does Not Contain' },
8778        { Value: 32, Text: 'Is Blank' },
8779        { Value: 64, Text: 'Is Not Blank' },
8780        { Value: 2048, Text: 'Ends With' }
8781    ];
8782    const GroupAll = 1;
8783    const GroupAny = 2;
8784    const GroupAllFalse = 3;
8785    const GroupAnyFalse = 4;
8786
8787    Rock.controls.valueFilter = (function () {
8788        //
8789        // Setup the UI so it reflects the initial data configured on the filter.
8790        //
8791        function loadInitialData($filter) {
8792            var data;
8793
8794            //
8795            // Try to parse the initial data.
8796            //
8797            try {
8798                data = JSON.parse($filter.find('input[type="hidden"]').val());
8799            }
8800            catch (ex) {
8801                data = null;
8802            }
8803
8804            //
8805            // Initialize a new filter if we have no current settings.
8806            //
8807            if (data === null) {
8808                data = { ExpressionType: GroupAny, Filters: [] };
8809                $filter.find('input[type="hidden"]').val(JSON.stringify(data));
8810            }
8811
8812            //
8813            // Grab the objects we need from the filter control.
8814            //
8815            var $filterTypeAll = $filter.data('filterTypeAll');
8816            var $filterTypeAny = $filter.data('filterTypeAny');
8817            var options = $filter.data('options');
8818
8819            //
8820            // Set the initial state of the Any/All toggle buttons.
8821            //
8822            if (data.ExpressionType === GroupAll) {
8823                $filterTypeAll.addClass(options.btnToggleOnClass).removeClass(options.btnToggleOffClass);
8824                $filterTypeAny.addClass(options.btnToggleOffClass).removeClass(options.btnToggleOnClass);
8825            }
8826            else {
8827                $filterTypeAny.addClass(options.btnToggleOnClass).removeClass(options.btnToggleOffClass);
8828                $filterTypeAll.addClass(options.btnToggleOffClass).removeClass(options.btnToggleOnClass);
8829            }
8830
8831            //
8832            // Add the initial field rows.
8833            //
8834            for (var index in data.Filters) {
8835                var expression = data.Filters[index];
8836
8837                addFilterRow($filter, expression.Value, expression.Comparison);
8838            }
8839
8840            if (data.Filters.length === 0) {
8841                addFilterRow($filter, '', 8);
8842                updateData($filter);
8843            }
8844        }
8845
8846        //
8847        // Setup the initial event handlers for the controls in filter.
8848        //
8849        function addControlEvents($filter) {
8850            //
8851            // Load all the objects we need from the filter.
8852            //
8853            var $filterTypeAll = $filter.data('filterTypeAll');
8854            var $filterTypeAny = $filter.data('filterTypeAny');
8855            var options = $filter.data('options');
8856
8857            //
8858            // Add a click handler on the "Any" toggle button that will turn off the
8859            // "All" option and turn on the "Any" option instead.
8860            //
8861            $filterTypeAny.on('click', function (e) {
8862                e.preventDefault();
8863
8864                $filterTypeAny.addClass(options.btnToggleOnClass).removeClass(options.btnToggleOffClass);
8865                $filterTypeAll.addClass(options.btnToggleOffClass).removeClass(options.btnToggleOnClass);
8866
8867                updateData($filter);
8868            });
8869
8870            //
8871            // Add a click handler on the "All" toggle button that will turn off the
8872            // "Any" option and turn on the "All" option instead.
8873            //
8874            $filterTypeAll.on('click', function (e) {
8875                e.preventDefault();
8876
8877                $filterTypeAll.addClass(options.btnToggleOnClass).removeClass(options.btnToggleOffClass);
8878                $filterTypeAny.addClass(options.btnToggleOffClass).removeClass(options.btnToggleOnClass);
8879
8880                updateData($filter);
8881            });
8882
8883            //
8884            // Add a click handler to the "Add" button that will add a new filter row.
8885            //
8886            $filter.data('addButton').on('click', function (e) {
8887                e.preventDefault();
8888                addFilterRow($filter, '', 8);
8889                updateData($filter);
8890            });
8891        }
8892
8893        //
8894        // Add a new field row.
8895        //
8896        function addFilterRow($filter, value, type) {
8897            var options = $filter.data('options');
8898            var $comparisonButton = null;
8899
8900            //
8901            // Create the row container as an input-group so things look pretty.
8902            //
8903            var $row = $('<div class="input-group margin-b-sm"></div>');
8904
8905            //
8906            // Create the text field the user can type into.
8907            //
8908            var $text = $('<input type="text" class="form-control">').appendTo($row);
8909
8910            //
8911            // Create the drop down that indicates what type of filter row this is.
8912            //
8913            var $inputGroupBtns = $('<span class="input-group-btn" />').appendTo($row);
8914            var $fieldTypeBtn = $('<button type="button" class="btn btn-default dropdown-toggle filter-dropdown-toggle" data-toggle="dropdown"><span /> <span class="caret" /></button>')
8915                .appendTo($inputGroupBtns);
8916            var $fieldTypeOptions = $('<ul class="dropdown-menu dropdown-menu-right"></ul>')
8917                .appendTo($inputGroupBtns);
8918            for (var i in options.comparisonTypes) {
8919                var $btn = $('<li><a href="#" data-value="' + options.comparisonTypes[i].Value + '">' + options.comparisonTypes[i].Text + '</a></li>').appendTo($fieldTypeOptions);
8920                if (type === options.comparisonTypes[i].Value) {
8921                    $comparisonButton = $btn;
8922                }
8923            }
8924            $comparisonButton = $comparisonButton || $fieldTypeOptions.find('li:first-child');
8925
8926            //
8927            // Create the delete button.
8928            //
8929            $('<button type="button" class="btn btn-danger btn-square"><i class="ti ti-x"></i></button>')
8930                .appendTo($inputGroupBtns);
8931
8932            //
8933            // Set default values.
8934            //
8935            $text.val(value);
8936            changeFilterRowType($filter, $comparisonButton, true);
8937
8938            //
8939            // Setup event handlers.
8940            //
8941            addFilterRowEvents($filter, $row);
8942
8943            //
8944            // Append this new filter row to the DOM.
8945            //
8946            $filter.data('rowContainer').append($row);
8947        }
8948
8949        //
8950        // Setup the event handlers for a newly created filter row.
8951        //
8952        function addFilterRowEvents($filter, $field) {
8953            //
8954            // When the text has changed, update the stored data.
8955            //
8956            $field.find('input[type="text"]').on('change', function () {
8957                updateData($filter);
8958            });
8959
8960            //
8961            // When the user clicks on one of the filter type drop down options, update
8962            // the filter row type.
8963            //
8964            $field.find('ul > li > a').on('click', function (e) {
8965                e.preventDefault();
8966                changeFilterRowType($filter, $(this));
8967            });
8968
8969            //
8970            // Remove a single row from the filter.
8971            //
8972            $field.find('button.btn-danger').on('click', function (e) {
8973                e.preventDefault();
8974                $(this).closest('.input-group').remove();
8975                updateData($filter);
8976            });
8977        }
8978
8979        //
8980        // Update the filter row type based on what the user clicked.
8981        //
8982        function changeFilterRowType($filter, $btn, skipUpdate) {
8983            var $btnGroup = $btn.closest('.input-group-btn');
8984            $btnGroup.find('.dropdown-toggle span:first-child').text($btn.text());
8985
8986            if ($btn.data('value') === 32 || $btn.data('value') === 64) {
8987                $filter.find('input[type="text"]').val('').prop('disabled', true);
8988            }
8989            else {
8990                $filter.find('input[type="text"]').prop('disabled', false);
8991            }
8992
8993            if (skipUpdate !== true) {
8994                updateData($filter);
8995            }
8996        }
8997
8998        //
8999        // Update the hidden field to match what is in the UI.
9000        //
9001        function updateData($filter) {
9002            var options = $filter.data('options');
9003            var filters = [];
9004
9005            //
9006            // Walk each row of the filter and build up the filters collection.
9007            //
9008            $filter.data('rowContainer').find('.input-group').each(function (index, element) {
9009                var $field = $(element);
9010                var text = $field.find('input[type="text"]').val();
9011                var typeText = $field.find('button.dropdown-toggle span:first-child').text();
9012                var type = $field.find('ul li a').filter(function () { return $(this).text() === typeText; }).data('value');
9013
9014                filters.push({ Value: text, Comparison: type });
9015            });
9016
9017            //
9018            // If there is only one filter, it is a Contains filter and has no value, then get rid of it.
9019            //
9020            if (filters.length === 1 && filters[0].Value === '' && filters[0].Comparison === 8) {
9021                filters = [];
9022            }
9023
9024            //
9025            // Build the final filtered text value.
9026            //
9027            var data = {
9028                ExpressionType: $filter.data('filterTypeAny').hasClass(options.btnToggleOnClass) ? GroupAny : GroupAll,
9029                Filters: filters
9030            };
9031
9032            $filter.find('input[type="hidden"]').val(JSON.stringify(data));
9033        }
9034
9035        var exports = {
9036            initialize: function (options) {
9037                if (!options.controlId) {
9038                    throw 'controlId is required';
9039                }
9040
9041                //
9042                // Make sure we have minimum default options
9043                //
9044                options = $.extend({
9045                    btnToggleOnClass: 'btn-info',
9046                    btnToggleOffClass: 'btn-default',
9047                    required: true,
9048                    requiredMessage: 'The field is required',
9049                    hideFilterMode: false,
9050                    comparisonTypes: DefaultComparisonTypes,
9051                    defaultComparison: 8
9052                }, options);
9053
9054                //
9055                // Setup the comparsionTypesByValue object.
9056                //
9057                options.comparisonTypeByValue = {};
9058                for (var i = 0; i < options.comparisonTypes.length; i++) {
9059                    options.comparisonTypeByValue[options.comparisonTypes[i].Value] = options.comparisonTypes[i].Text;
9060                }
9061
9062                //
9063                // Find the filter control placeholder.
9064                //
9065                var $filter = $('#' + options.controlId);
9066
9067                //
9068                // Setup the "Any/All" buttons for the filter.
9069                //
9070                var $filterTypeContainer = $('<div class="text-right" />').appendTo($filter);
9071                var $filterTypeGroup = $('<div class="btn-group" role="group" />').appendTo($filterTypeContainer);
9072                var $filterTypeAny = $('<button type="button" class="btn btn-default btn-xs">Any</button>').appendTo($filterTypeGroup);
9073                var $filterTypeAll = $('<button type="button" class="btn btn-default btn-xs">All</button>').appendTo($filterTypeGroup);
9074                if (options.hideFilterMode === true) {
9075                    $filterTypeContainer.addClass('hidden');
9076                }
9077
9078                //
9079                // Setup the placeholder div that will contain all the filter  rows.
9080                //
9081                var $rowContainer = $('<div />').appendTo($filter);
9082
9083                //
9084                // Setup the add button.
9085                //
9086                var $addButtonContainer = $('<div class="text-right" />').appendTo($filter);
9087                var $addButton = $('<a href="#" class="btn btn-default btn-square btn-sm"><i class="ti ti-plus"></i></a>').appendTo($addButtonContainer);
9088
9089                //
9090                // Store all the objects we need access to later as jQuery data objects
9091                // on the filter control.
9092                //
9093                $filter.data('options', options);
9094                $filter.data('filterTypeAny', $filterTypeAny);
9095                $filter.data('filterTypeAll', $filterTypeAll);
9096                $filter.data('rowContainer', $rowContainer);
9097                $filter.data('addButton', $addButton);
9098
9099                //
9100                // Setup initial control events.
9101                //
9102                addControlEvents($filter);
9103
9104                //
9105                // Setup UI to reflect initial data.
9106                //
9107                loadInitialData($filter);
9108            },
9109            clientValidate: function (validator, args) {
9110                var $filter = $(validator).prev();
9111                var required = $filter.data('options').required;
9112                var data = JSON.parse($filter.find('input[type="hidden"]').val());
9113
9114                var isValid = !required || data.Filters.length > 0;
9115
9116                if (isValid) {
9117                    $filter.closest('.form-group').removeClass('has-error');
9118                    args.IsValid = true;
9119                }
9120                else {
9121                    $filter.closest('.form-group').addClass('has-error');
9122                    args.IsValid = false;
9123                    validator.errormessage = $filter.data('options').requiredMessage;
9124                }
9125            }
9126        };
9127
9128        return exports;
9129    }());
9130}(jQuery));
9131;
9132; (function () {
9133  function updateKeyValues(e) {
9134    var $span = e.closest('span.value-list');
9135    var newValue = '';
9136
9137    var valueDelimiters = ['|', ','];
9138    $span.children('span.value-list-rows').first().children('div.controls-row').each(function (index) {
9139      var value = $(this).children('.js-value-list-input').first().val();
9140
9141      valueDelimiters.forEach(function (v, i, a) {
9142        var re = new RegExp('\\' + v, 'g');
9143        if (value.indexOf(v) > -1) {
9144          value = value.replace(re, encodeURIComponent(v));
9145        }
9146      });
9147      newValue += value + '|'
9148    });
9149
9150    $span.children('input').first().val(newValue);
9151  }
9152
9153  Sys.Application.add_load(function () {
9154
9155    $('a.value-list-add').on('click', function (e) {
9156      e.preventDefault();
9157      var $ValueList = $(this).closest('.value-list');
9158      var newValuePickerHtml = $ValueList.find('.js-value-list-html').val()
9159      $ValueList.find('.value-list-rows').append(newValuePickerHtml);
9160      updateKeyValues($(this));
9161      Rock.controls.modal.updateSize($(this));
9162    });
9163
9164    $(document).on('click', 'a.value-list-remove', function (e) {
9165      e.preventDefault();
9166      var $rows = $(this).closest('span.value-list-rows');
9167      $(this).closest('div.controls-row').remove();
9168      updateKeyValues($rows);
9169      Rock.controls.modal.updateSize($(this));
9170    });
9171
9172    $(document).on('change', '.js-value-list-input', function (e) {
9173      updateKeyValues($(this));
9174    });
9175  });
9176})();
9177;
9178(function (Sys) {
9179    'use strict';
9180    Sys.Application.add_load(function () {
9181        $('a.warning').on('click', function (e) {
9182            e.preventDefault();
9183            $(this).siblings('div.alert-warning').slideToggle(function () {
9184                Rock.controls.modal.updateSize(this);
9185            });
9186            $(this).siblings('div.alert-info').slideUp();
9187        });
9188    });
9189}(Sys));
9190;
9191/* NOTE: Requires this on the init of the block
9192
9193RockPage.AddScriptLink("~/Scripts/d3-cloud/d3.layout.cloud.js");
9194RockPage.AddScriptLink("~/Scripts/d3-cloud/d3.min.js");
9195
9196*/
9197
9198(function ($)
9199{
9200  'use strict';
9201  window.Rock = window.Rock || {};
9202  Rock.controls = Rock.controls || {};
9203
9204  Rock.controls.wordcloud = (function ()
9205  {
9206    
9207    var exports = {
9208      initialize: function (options)
9209      {
9210        if (!options.inputTextId) {
9211          throw 'inputTextId is required';
9212        }
9213
9214        if (!options.visId) {
9215          throw 'visId is required';
9216        }
9217
9218        // if the visId element isn't found, jump out
9219        if (d3.select("#" + options.visId).empty()) {
9220          return;
9221        }
9222
9223        var $inputText = $('#' + options.inputTextId);
9224        var visId = options.visId;
9225        var statusId = options.statusId;
9226        var svgWidth = options.width || 800;
9227        var svgHeight = options.height || 300;
9228        var fontName = options.fontName || 'Impact';
9229        var maxWords = options.maxWords || 255;
9230        var scaleName = options.scaleName || 'log';
9231        var spiralName = options.spiralName || 'archimedean';
9232        var colors = options.colors || ['#0193B9', '#F2C852', '#1DB82B', '#2B515D', '#ED3223'];
9233
9234        var angleCount = options.angleCount == undefined ? 6 : options.angleCount;
9235        if (angleCount > 180) {
9236          angleCount = 180;
9237        }
9238
9239        // a good max range is -90 to 90 so that words don't go upside down
9240        var angleMin = options.angleMin == undefined ? -90 : options.angleMin;
9241        var angleMax = options.angleMax == undefined ? 90 : options.angleMax;
9242        var angles = [];
9243        if (angleCount == 1) {
9244          // one angle, so choose the midpoint of the angleMin and angleMax
9245          angles.push((angleMax + angleMin) / 2);
9246        }
9247        else if (angleCount == 2) {
9248          // two angles, so choose the angleMin and angleMax
9249          angles.push(angleMin);
9250          angles.push(angleMax);
9251        }
9252        else if (angleCount > 2) {
9253          // three or more, so choose the angleMin and angleMax, and the the remaining ones between
9254          var angleSize = (angleMax - angleMin) / (angleCount - 1)
9255          var angle = angleMin;
9256          while (angle <= angleMax) {
9257            angles.push(angle);
9258            angle += angleSize;
9259          }
9260        }
9261
9262        /* from cloud.js */
9263        var unicodePunctuationRe = "!-#%-*,-/:;?@\\[-\\]_{}\xa1\xa7\xab\xb6\xb7\xbb\xbf\u037e\u0387\u055a-\u055f\u0589\u058a\u05be\u05c0\u05c3\u05c6\u05f3\u05f4\u0609\u060a\u060c\u060d\u061b\u061e\u061f\u066a-\u066d\u06d4\u0700-\u070d\u07f7-\u07f9\u0830-\u083e\u085e\u0964\u0965\u0970\u0af0\u0df4\u0e4f\u0e5a\u0e5b\u0f04-\u0f12\u0f14\u0f3a-\u0f3d\u0f85\u0fd0-\u0fd4\u0fd9\u0fda\u104a-\u104f\u10fb\u1360-\u1368\u1400\u166d\u166e\u169b\u169c\u16eb-\u16ed\u1735\u1736\u17d4-\u17d6\u17d8-\u17da\u1800-\u180a\u1944\u1945\u1a1e\u1a1f\u1aa0-\u1aa6\u1aa8-\u1aad\u1b5a-\u1b60\u1bfc-\u1bff\u1c3b-\u1c3f\u1c7e\u1c7f\u1cc0-\u1cc7\u1cd3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205e\u207d\u207e\u208d\u208e\u2329\u232a\u2768-\u2775\u27c5\u27c6\u27e6-\u27ef\u2983-\u2998\u29d8-\u29db\u29fc\u29fd\u2cf9-\u2cfc\u2cfe\u2cff\u2d70\u2e00-\u2e2e\u2e30-\u2e3b\u3001-\u3003\u3008-\u3011\u3014-\u301f\u3030\u303d\u30a0\u30fb\ua4fe\ua4ff\ua60d-\ua60f\ua673\ua67e\ua6f2-\ua6f7\ua874-\ua877\ua8ce\ua8cf\ua8f8-\ua8fa\ua92e\ua92f\ua95f\ua9c1-\ua9cd\ua9de\ua9df\uaa5c-\uaa5f\uaade\uaadf\uaaf0\uaaf1\uabeb\ufd3e\ufd3f\ufe10-\ufe19\ufe30-\ufe52\ufe54-\ufe61\ufe63\ufe68\ufe6a\ufe6b\uff01-\uff03\uff05-\uff0a\uff0c-\uff0f\uff1a\uff1b\uff1f\uff20\uff3b-\uff3d\uff3f\uff5b\uff5d\uff5f-\uff65",
9264            fill = d3.scaleLinear()
9265            .domain([0, 1, 2, 3, 4, 5, 6, 10, 15, 20, 100])
9266            .range(d3.schemeCategory20b),
9267            w = svgWidth,
9268            h = svgHeight,
9269            words = [],
9270            max, scale = 1,
9271            complete = 0,
9272            keyword = "",
9273            tags, fontSize, maxLength = 30,
9274            fetcher, statusText = d3.select("#" + statusId),
9275            layout = d3.layout.cloud().timeInterval(10).size([w, h]).fontSize(function (t)
9276            {
9277              return fontSize(+t.value)
9278            }).text(function (t)
9279            {
9280              return t.key
9281            }).on("word", progress).on("end", draw),
9282            svg = d3.select("#" + visId).append("svg").attr("width", w).attr("height", h),
9283            background = svg.append("g"),
9284            vis = svg.append("g").attr("transform", "translate(" + [w >> 1, h >> 1] + ")");
9285
9286        var stopWords = /^(i|me|my|myself|we|us|our|ours|ourselves|you|your|yours|yourself|yourselves|he|him|his|himself|she|her|hers|herself|it|its|itself|they|them|their|theirs|themselves|what|which|who|whom|whose|this|that|these|those|am|is|are|was|were|be|been|being|have|has|had|having|do|does|did|doing|will|would|should|can|could|ought|i'm|you're|he's|she's|it's|we're|they're|i've|you've|we've|they've|i'd|you'd|he'd|she'd|we'd|they'd|i'll|you'll|he'll|she'll|we'll|they'll|isn't|aren't|wasn't|weren't|hasn't|haven't|hadn't|doesn't|don't|didn't|won't|wouldn't|shan't|shouldn't|can't|cannot|couldn't|mustn't|let's|that's|who's|what's|here's|there's|when's|where's|why's|how's|a|an|the|and|but|if|or|because|as|until|while|of|at|by|for|with|about|against|between|into|through|during|before|after|above|below|to|from|up|upon|down|in|out|on|off|over|under|again|further|then|once|here|there|when|where|why|how|all|any|both|each|few|more|most|other|some|such|no|nor|not|only|own|same|so|than|too|very|say|says|said|shall)$/,
9287          punctuation = new RegExp("[" + unicodePunctuationRe + "]", "g"),
9288          wordSeparators = /[ \f\n\r\t\v\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000\u3031-\u3035\u309b\u309c\u30a0\u30fc\uff70]+/g,
9289          discard = /^(@|https?:|\/\/)/,
9290          htmlTags = /(<[^>]*?>|<script.*?<\/script>|<style.*?<\/style>|<head.*?><\/head>)/g,
9291          matchTwitter = /^https?:\/\/([^\.]*\.)?twitter\.com/;
9292
9293        var inputText = $inputText.val();
9294        if (inputText) {
9295          parseText(inputText);
9296        }
9297
9298        function flatten(t, e)
9299        {
9300          if ("string" == typeof t) return t;
9301          var n = [];
9302          for (e in t) {
9303            var a = flatten(t[e], e);
9304            a && n.push(a)
9305          }
9306          return n.join(" ")
9307        }
9308
9309        function parseText(t)
9310        {
9311          tags = {};
9312          var e = {},
9313              n = false//d3.select("#per-line").property("checked");
9314          t.split(n ? /\n/g : wordSeparators).forEach(function (t)
9315          {
9316            discard.test(t) || (n || (t = t.replace(punctuation, "")), stopWords.test(t.toLowerCase()) || (t = t.substr(0, maxLength), e[t.toLowerCase()] = t, tags[t = t.toLowerCase()] = (tags[t] || 0) + 1))
9317          }), tags = d3.entries(tags).sort(function (t, e)
9318          {
9319            return e.value - t.value
9320          }), tags.forEach(function (t)
9321          {
9322            t.key = e[t.key]
9323          }), generate()
9324        }
9325
9326        function generate()
9327        {
9328          layout
9329            .font(fontName)
9330            .spiral(spiralName)
9331            .rotate(function ()
9332            {
9333              return angles[Math.floor(Math.random() * angles.length)];
9334            }),
9335          fontSize = d3.scaleLog().range([10, 100]), tags.length && fontSize.domain([+tags[tags.length - 1].value || 1, +tags[0].value]),
9336          complete = 0, statusText.style("display", null),
9337          words = [],
9338          layout.stop().words(tags.slice(0, max = Math.min(tags.length, +maxWords))).start()
9339        }
9340
9341        function progress(t)
9342        {
9343          statusText.text(++complete + "/" + max)
9344        }
9345
9346        function draw(t, e)
9347        {
9348          statusText.style("display", "none"),
9349          scale = e ? Math.min(w / Math.abs(e[1].x - w / 2), w / Math.abs(e[0].x - w / 2), h / Math.abs(e[1].y - h / 2), h / Math.abs(e[0].y - h / 2)) / 2 : 1, words = t;
9350          var n = vis.selectAll("text").data(words, function (t)
9351          {
9352            return t.text.toLowerCase()
9353          });
9354          n.transition().duration(1e3).attr("transform", function (t)
9355          {
9356            return "translate(" + [t.x, t.y] + ")rotate(" + t.rotate + ")"
9357          }).style("font-size", function (t)
9358          {
9359            return t.size + "px"
9360          }),
9361
9362          n.enter().append("text").attr("text-anchor", "middle").attr("transform", function (t)
9363          {
9364            return "translate(" + [t.x, t.y] + ")rotate(" + t.rotate + ")"
9365          }).style("font-size", "1px").transition().duration(1e3).style("font-size", function (t)
9366          {
9367            return t.size + "px"
9368          }).style("font-family", function (t)
9369          {
9370            return t.font
9371          }).text(function (t)
9372          {
9373            return t.text
9374          }).style("fill", function (t)
9375          {
9376              return colors[Math.floor(Math.random() * colors.length)];
9377          });
9378
9379          var a = background.append("g").attr("transform", vis.attr("transform")),
9380              r = a.node();
9381          n.exit().each(function ()
9382          {
9383            r.appendChild(this)
9384          }), a.transition().duration(1e3).style("opacity", 1e-6).remove(), vis.transition().delay(1e3).duration(750).attr("transform", "translate(" + [w >> 1, h >> 1] + ")scale(" + scale + ")")
9385        }
9386
9387      }
9388    }
9389
9390    return exports;
9391  }());
9392}(jQuery));
9393;
9394(function ($) {
9395    'use strict';
9396    window.Rock = window.Rock || {};
9397    Rock.controls = Rock.controls || {};
9398
9399    Rock.controls.yearPicker = (function () {
9400        var exports = {
9401            initialize: function (options) {
9402                if (!options.id) {
9403                    throw 'id is required';
9404                }
9405
9406                var earliestDate = new Date(1000, 1, 1);
9407
9408                // uses https://github.com/eternicode/bootstrap-datepicker
9409                $('#' + options.id).datepicker({
9410                    format: 'yyyy',
9411                    assumeNearbyYear: 10,
9412                    autoclose: true,
9413                    startView: 2,
9414                    minViewMode: 2,
9415                    startDate: earliestDate,
9416                    zIndexOffset: 1050
9417                });
9418
9419            }
9420        };
9421
9422        return exports;
9423    }());
9424}(jQuery));
9425;
9426var loadFunction = function () {
9427    // handle what should happen when a different compare type is selected
9428    function updateFilterControls(filterCompareControl) {
9429        var $fieldCriteriaRow = $(filterCompareControl).closest('.field-criteria');
9430        var compareValue = $(filterCompareControl).val();
9431        var isNullCompare = (compareValue == 32 || compareValue == 64);
9432        var isBetweenCompare = (compareValue == 4096);
9433        if (isNullCompare) {
9434            $fieldCriteriaRow.find('.js-filter-control').hide();
9435            $fieldCriteriaRow.find('.js-filter-control-between').hide();
9436        }
9437        else if (isBetweenCompare) {
9438            $fieldCriteriaRow.find('.js-filter-control').hide();
9439            $fieldCriteriaRow.find('.js-filter-control-between').show();
9440        }
9441        else {
9442            $fieldCriteriaRow.find('.js-filter-control').show();
9443            $fieldCriteriaRow.find('.js-filter-control-between').hide();
9444        }
9445    }
9446
9447    $('.js-filter-compare').each(function (e) {
9448        updateFilterControls(this);
9449    });
9450
9451    $('.js-filter-compare').on("change", function () {
9452        updateFilterControls(this);
9453    });
9454
9455    // handle property selection changes from the EntityFieldFilter
9456    $('select.entity-property-selection').on("change", function () {
9457        var $parentRow = $(this).closest('.js-filter-row');
9458        $parentRow.find('div.field-criteria').hide();
9459        $parentRow.find('div.field-criteria').eq($(this).find(':selected').index()).show();
9460    });
9461
9462    // activity animation on filter field cootrol
9463    $('.filter-item > header').on("click", function () {
9464        $(this).siblings('.panel-body').slideToggle();
9465        $(this).children('div.pull-left').children('div').slideToggle();
9466
9467        $expanded = $(this).children('input.filter-expanded');
9468        $expanded.val($expanded.val() == 'True' ? 'False' : 'True');
9469
9470        $('a.filter-view-state > i', this).toggleClass('ti-chevron-down');
9471        $('a.filter-view-state > i', this).toggleClass('ti-chevron-up');
9472    });
9473
9474    // fix so that the Remove button will fire its event, but not the parent event
9475    $('.filter-item a.btn-danger').on("click", function (event) {
9476        event.stopImmediatePropagation();
9477    });
9478
9479    $('.filter-item-select').on("click", function (event) {
9480        event.stopImmediatePropagation();
9481    });
9482}
9483
9484$(document).ready(function () {
9485    loadFunction();
9486    Sys.Application.add_load(loadFunction);
9487});
9488
9489//
9490(function ($) {
9491    'use strict';
9492    window.Rock = window.Rock || {};
9493
9494    Rock.reporting = (function () {
9495        var _reporting = {},
9496            exports = {
9497                //
9498                formatFilterForDateField: function (title, $selectedContent) {
9499                    var betweenMode = $('.js-filter-control-between', $selectedContent).is(':visible');
9500                    var dateValue = '';
9501                    if (betweenMode) {
9502                        return title + ' during ' + $('.js-slidingdaterange-text-value', $selectedContent).val();
9503                    } else {
9504                        var useCurrentDateOffset = $('.js-current-date-checkbox', $selectedContent).is(':checked');
9505
9506                        if (useCurrentDateOffset) {
9507                            var daysOffset = $('.js-current-date-offset', $selectedContent).val();
9508                            if (daysOffset > 0) {
9509                                dateValue = 'Current Date plus ' + daysOffset + ' days';
9510                            }
9511                            else if (daysOffset < 0) {
9512                                dateValue = 'Current Date minus ' + -daysOffset + ' days';
9513                            }
9514                            else {
9515                                dateValue = 'Current Date';
9516                            }
9517                        }
9518                        else {
9519                            dateValue = $('.js-date-picker input', $selectedContent).filter(':visible').val();
9520                        }
9521                        return title + ' ' + $('.js-filter-compare', $selectedContent).find(':selected').text() + ' \'' + dateValue + '\''
9522                    }
9523                },
9524
9525                //
9526                formatFilterForDateTimeField: function (title, $selectedContent) {
9527                    var betweenMode = $('.js-filter-control-between', $selectedContent).is(':visible');
9528                    var dateValue = '';
9529                    var timeValue = '';
9530                    if (betweenMode) {
9531                        return title + ' during ' + $('.js-slidingdaterange-text-value', $selectedContent).val();
9532                    } else {
9533                        var useCurrentDateOffset = $('.js-current-datetime-checkbox', $selectedContent).is(':checked');
9534
9535                        if (useCurrentDateOffset) {
9536                            var minutesOffset = Number($('.js-current-datetime-offset', $selectedContent).val());
9537                            if (minutesOffset > 0) {
9538                                dateValue = 'Current Time plus ' + minutesOffset + ' minutes';
9539                            }
9540                            else if (minutesOffset < 0) {
9541                                dateValue = 'Current Time minus ' + -minutesOffset + ' minutes';
9542                            }
9543                            else {
9544                                dateValue = 'Current Time';
9545                            }
9546                        }
9547                        else {
9548                            dateValue = $('input.js-datetime-date', $selectedContent).filter(':visible').val() || '';
9549                            timeValue = $('input.js-datetime-time', $selectedContent).filter(':visible').val() || '';
9550                        }
9551
9552                        var dateTimeValue = (dateValue + ' ' + timeValue).trim();
9553                        return title + ' ' + $('.js-filter-compare', $selectedContent).find(':selected').text() + ' \'' + dateTimeValue + '\''
9554                    }
9555                },
9556
9557                //
9558                formatFilterForCheckBoxListFilterControl: function (title, $selectedContent) {
9559                    var selectedItems = '';
9560                    $('input:checked', $selectedContent).each(
9561                        function () {
9562                            selectedItems += selectedItems == '' ? '' : ' OR ';
9563                            selectedItems += ' \'' + $(this).parent().text() + '\'';
9564                        });
9565
9566                    return title + ' is ' + selectedItems
9567                },
9568
9569                //
9570                formatFilterForDefinedValueField: function (title, $selectedContent) {
9571                    return formatFilterForCheckBoxListFilterControl(title, $selectedContent);
9572                },
9573
9574                // NOTE: this is specifically for the Rock.Reporting.DataFilter.OtherDataViewFilter (and similar) components
9575                formatFilterForOtherDataViewFilter: function (title, $selectedContent) {
9576                    var dataViewName = $('.js-dataview .js-item-name-value', $selectedContent).val();
9577                    return title + ' ' + dataViewName;
9578                },
9579
9580                //
9581                formatFilterForSelectSingleField: function (title, $selectedContent) {
9582                    return formatFilterForCheckBoxListFilterControl(title, $selectedContent);
9583                },
9584
9585                // NOTE: this is specifically for the Rock.Reporting.DataFilter.Person.InGroupFilter component
9586                formatFilterForGroupFilterField: function (title, $selectedContent) {
9587                    var groupNames = $('.js-group-picker', $selectedContent).find('.selected-names').text();
9588                    var checkedRoles = $('.js-roles', $selectedContent).find(':checked').closest('label');
9589                    var result = title + ' ' + groupNames;
9590                    var includeChildGroups = $('.js-include-child-groups', $selectedContent).is(':checked');
9591                    if (includeChildGroups) {
9592
9593                        var includeDescendantGroups = $('.js-include-child-groups-descendants', $selectedContent).is(':checked');
9594                        var includeSelectedGroups = $('.js-include-selected-groups', $selectedContent).is(':checked');
9595                        var includeInactiveGroups = $('.js-include-inactive-groups', $selectedContent).is(':checked');
9596                        if (includeDescendantGroups) {
9597                            result = result + ' OR descendant groups';
9598                        } else {
9599                            result = result + ' OR child groups';
9600                        }
9601
9602                        if (includeInactiveGroups) {
9603                            result += ", including inactive groups";
9604                        }
9605
9606                        if (!includeSelectedGroups) {
9607                            result = result + ', NOT including selected groups';
9608                        }
9609                    }
9610
9611                    if (checkedRoles.length > 0) {
9612                        var roleCommaList = checkedRoles.map(function () { { return $(this).text() } }).get().join(',');
9613                        result = result + ', with role(s): ' + roleCommaList;
9614                    }
9615
9616                    var groupMemberStatus = $('.js-group-member-status option:selected', $selectedContent).text();
9617                    if (groupMemberStatus) {
9618                        result = result + ', with member status:' + groupMemberStatus;
9619                    }
9620
9621                    var dateAddedDateRangeText = $('.js-dateadded-sliding-date-range .js-slidingdaterange-text-value', $selectedContent).val()
9622                    if (dateAddedDateRangeText) {
9623                      result = result + ', added to group in Date Range: ' + dateAddedDateRangeText;
9624                    }
9625
9626                    var firstAttendanceDateRangeText = $('.js-firstattendance-sliding-date-range .js-slidingdaterange-text-value', $selectedContent).val()
9627                    if (firstAttendanceDateRangeText) {
9628                      result = result + ', first attendance to group in Date Range: ' + firstAttendanceDateRangeText;
9629                    }
9630
9631                    var lastAttendanceDateRangeText = $('.js-lastattendance-sliding-date-range .js-slidingdaterange-text-value', $selectedContent).val()
9632                    if (lastAttendanceDateRangeText) {
9633                      result = result + ', last attendance to group in Date Range: ' + lastAttendanceDateRangeText;
9634                    }
9635
9636                    return result;
9637                },
9638
9639                // NOTE: this is specifically for the Rock.Reporting.DataFilter.Person.HasPhoneFilter component
9640                formatFilterForHasPhoneFilter: function ($content) {
9641
9642                    var has;
9643                    if ($('.js-hasphoneoftype', $content).find(':selected').val() == "True") {
9644                        has = "Has ";
9645                    } else {
9646                        has = "Doesn't have ";
9647                    }
9648
9649                    var phoneType = $('.js-phonetype', $content).find(':selected').text();
9650                    var sms = $('.js-hassms', $content).find(':selected').text();
9651
9652                    if (sms == 'Yes') {
9653                        sms = ' AND has SMS Enabled';
9654                    }
9655                    else if (sms == 'No') {
9656                        sms = " AND doesn't have SMS Enabled";
9657                    }
9658
9659                    var result = has + phoneType + sms;
9660
9661                    return result;
9662                },
9663
9664                //
9665                formatFilterDefault: function (title, $selectedContent) {
9666                    var compareTypeText = $('.js-filter-compare', $selectedContent).find(':selected').text();
9667
9668                    var compareValueText = $('input[type=text].js-filter-control', $selectedContent).val(); // textbox value.
9669                    if (!compareValueText || compareValueText == "") {
9670                        compareValueText = $('.js-filter-control', $selectedContent).find(':selected').map(function () { return this.text; }).get().join("', '");
9671                    }
9672                    if (!compareValueText || compareValueText == "") {
9673                        compareValueText = $('.js-filter-control', $selectedContent).find(':checked').next().map(function () { return $(this).text(); }).get().join("', '");
9674                    }
9675
9676                    var result = title;
9677                    if ($('.js-filter-control', $selectedContent).is(':visible')) {
9678                        result = title + ' ' + compareTypeText + " '" + compareValueText + "'";
9679                    } else {
9680                        result = title + ' ' + compareTypeText;
9681                    }
9682
9683                    return result;
9684                }
9685            };
9686
9687        return exports;
9688    }());
9689}(jQuery));
9690;

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.