PageSourceSearch

https://iris.bams.com/js/site.js?23ae28f7d4fade21882ceb0b8b09c3a8

js bams.com collected 2026-09-28 06:38:36 UTC 214,350 bytes, 6,345 lines download raw bytes

1$.ajaxSetup({
2    headers: {
3        'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
4    }
5});
6
7if (window.Echo) {
8    Echo.connector.pusher.connection.bind('connected', function () {
9        $.ajaxSetup({headers: {'X-Socket-Id': Echo.socketId()}});
10    });
11}
12
13//menu
14var timeout    = 500;
15var closetimer = 0;
16var ddmenuitem = 0;
17
18var popupReminderRescheduleTaskDuration = 300000;
19var popupReminderRescheduleTaskStartObj = false;
20var popupReminderRescheduleTaskSnoozeDD = false;
21var notificationsCount                  = 0;
22
23function jsddm_open() {
24    jsddm_canceltimer();
25    jsddm_close();
26    ddmenuitem = $(this).find('ul').css('visibility', 'visible');
27}
28
29function jsddm_close() {
30    if (ddmenuitem) ddmenuitem.css('visibility', 'hidden');
31}
32
33function jsddm_timer() {
34    closetimer = window.setTimeout(jsddm_close, timeout);
35}
36
37function jsddm_canceltimer() {
38    if (closetimer) {
39        window.clearTimeout(closetimer);
40        closetimer = null;
41    }
42}
43
44/** Cookie Setter and Getter **/
45
46function setCookie(cookieName, cookieValue) {
47    document.cookie = cookieName + '=' + cookieValue + '; path=/';
48}
49
50function getCookie(cookieName) {
51    var ca = document.cookie.split(';');
52    for (var i = 0; i < ca.length; i++) {
53        var c = ca[i];
54        while (c.charAt(0) == ' ') {
55            c = c.substring(1, c.length);
56        }
57        if (c.indexOf(cookieName) == 0) {
58            return c.substring(cookieName.length + 1, c.length);
59        }
60    }
61    return null;
62}
63
64/** Start Task Reminder Functions **/
65
66/**
67 * Gets latest notifications from server
68 *
69 * @returns {boolean}
70 */
71function refreshNotifications() {
72    // do not refresh reminders on dialer page
73    if (window.location.pathname === '/dialer' ||
74        location.pathname.indexOf('/appRawRequest/') !== -1 ||
75        location.pathname.startsWith('/tsys/managePricing/feeItem/')
76    ) {
77        return false;
78    }
79
80    var currentlyVisible = '';
81
82    // get visible notification ids
83    $('.custom-pnotify-notification').each(function() {
84        var notificationId = parseInt($(this).data('nid'));
85
86        if (! isNaN(notificationId) && notificationId > 0) {
87            currentlyVisible += notificationId + ',';
88        }
89    });
90
91    return $.ajax({
92        type: 'post',
93        url: '/notifications/getAll',
94        data: {
95            visibleNotifications: currentlyVisible
96        },
97        dataType: "json",
98        success: function (data) {
99            if (data) {
100                if (typeof data.notifications !== 'undefined') {
101                    if (typeof data.total !== 'undefined') {
102                        notificationsCount = data.total;
103                        if (typeof ClearAll !== 'undefined') {
104                            ClearAll.showClearAll();
105                        }
106                    }
107                    $.each(data.notifications, function (key, data) {
108                        if (data && data['data']) {
109                            var callToFunction = 'refresh' + key + 'Notification';
110                            setTimeout(function() {
111                                window[callToFunction](data['data'], data['params'], notificationsTopRightStack);
112                            }, 10);
113                        }
114                    });
115                }
116            }
117        },
118        error: function (jqXHR) {
119            if (jqXHR.responseJSON && jqXHR.responseJSON.length == 1 && jqXHR.responseJSON[0].indexOf('session expired') !== -1) {
120                window.location.reload();
121            }
122        }
123    });
124}
125
126/**
127 * Hides Task Reminder notification popup
128 */
129function hideTaskReminderPopup() {
130    if ($('#popup_reminder_newTasksDialog').length > 0) {
131        localStorage.removeItem('task_reminder');
132        $('#popup_reminder_newTasksDialog').closest('.ui-pnotify').remove();
133    }
134    ClearAll.showClearAll();
135    refreshNotifications();
136}
137
138function preventEvent(event) {
139    if (typeof event !== 'object') {
140        return;
141    }
142    if (event.preventDefault instanceof Function) {
143        event.preventDefault();
144    }
145    if (event.stopPropagation instanceof Function) {
146        event.stopPropagation();
147    }
148}
149
150var cachedUserDropDownInputs = {};
151
152function initUserAutocompleteDropDowns() {
153    $('.userDropDownAutoComplete').each(function() {
154        var $obj    = $(this);
155        var token   = $obj.attr('data-token');
156        var options = false;
157        var id      = $obj.attr('id');
158
159        if (token in window.userDropDownAutoCompleteParams) {
160            options = window.userDropDownAutoCompleteParams[token];
161        }
162
163        if (token !== undefined && token.length && options != false) {
164            $obj.select2({
165                dropdownParent: $obj.parents('.sh-advanced').length===0 ? ($obj.parents('.select2-dropdown-parent').length === 0 ? null : $obj.parents('.select2-dropdown-parent')) : $obj.parent().parent(),
166                placeholder: "Search User",
167                minimumInputLength: 3,
168                ajax: {
169                    type: 'post',
170                    url: '/ajax/userDropDown',
171                    dataType: 'json',
172                    data: function (params) {
173                        return {
174                            search: params.term,
175                            options: JSON.stringify(options),
176                            token: token
177                        };
178                    },
179                    delay: 250,
180                    cache: true
181                }
182            });
183
184            $('#' + id).on('select2:open', function() {
185                document.querySelector('.select2-container--open .select2-search__field').focus();
186
187                if (cachedUserDropDownInputs[id] !== undefined) {
188                    $('#select2-' + id + '-results').closest('.select2-container').find('.select2-search input').focus().val(cachedUserDropDownInputs[id]).trigger('input');
189                }
190            }).on('select2:closing', function() {
191                cachedUserDropDownInputs[id] = $('#select2-' + id + '-results').closest('.select2-container').find('.select2-search input').val();
192            }).on('select2:select', function() {
193                if (cachedUserDropDownInputs[id] !== undefined) {
194                    delete cachedUserDropDownInputs[id];
195                }
196            });
197        }
198    });
199}
200
201function fixReorderedColumnHeader(tableObj) {
202    if(navigator.userAgent.toLowerCase().indexOf('firefox') > -1){
203        var tableBody = tableObj.find('.dataTables_scrollBody');
204        tableObj.on('order.dt', function() {
205            var scrollLeft = tableBody.scrollLeft();
206            setTimeout(function() {
207                tableBody.scrollLeft(scrollLeft);
208            }, 10);
209        });
210    }
211}
212
213$(function() {
214
215    $(document).on('keypress', '.ui-dialog', function(e) {
216        if(e.keyCode == 13 && e.target.tagName != 'TEXTAREA' && e.target.tagName != 'SELECT' && !$(e.target).find('.chosen-container').hasClass('chosen-container-active')) {
217            $(this).find('.dialog-submit-button').not('.ui-button-disabled').click();
218        }
219    });
220
221    $('button.go-to-admin').on('click', function(e) {
222        e.stopPropagation();
223        var adminUrl = sessionStorage.getItem('adminUrl');
224        var referrerUrl = document.referrer;
225        var currentUrl = window.location.href;
226        if(adminUrl) {
227            if(adminUrl==referrerUrl || adminUrl==currentUrl || (referrerUrl.split('/').slice(0,5).join('/')==currentUrl.split('/').slice(0,5).join('/'))) {
228                window.location.href = adminUrl;
229            } else {
230                window.location.href = '/manage/index';
231            }
232        } else {
233            window.location.href = '/manage/index';
234        }
235    });
236
237    $(document).on('click', '.ui-button', function() {
238        $(this).blur();
239    });
240
241    if ($('#notifications_wrapper').find('.current_user_name').length > 0) {
242        // set notifications refresh interval to 15 seconds
243        new PeriodicTask(15, function(){
244            if (! document.hidden) {
245                return refreshNotifications();
246            }
247
248            return $.when();
249        });
250    }
251
252    $(document).on('click', 'div.reminder-content.rc-active .lead-reminder a.lead_link', function () {
253        var taskID = $(this).attr('id').split('_').pop();
254        var bRedirect = false;
255
256        if (taskID) {
257            $.ajax({
258                type: 'post',
259                url: '/reminders/markTaskCompleted',
260                async: false,
261                data: {
262                    tids: [taskID]
263                },
264                success: function (result) {
265                    if (result) {
266                        bRedirect = true;
267                    }
268                }
269            });
270        }
271
272        return bRedirect;
273    });
274
275    $(document).on('click', '.rw-down', function () {
276        var $this = $('.rc-active');
277        $this.animate({'top': '-270px'}).removeClass('rc-active');
278        $this.next().animate({'top': '0'}).addClass('rc-active');
279        $('.rw-up').css('display', 'block');
280        if (!$this.next().next().hasClass('reminder-content')) {
281            $(this).hide();
282        } else {
283            $(this).show();
284        }
285        $('#popup_reminder_title').text($this.next().data('type') + ' Approaching');
286        $("#popup_reminder_reschedule_dialog").hide();
287    });
288
289    $(document).on('click', '.rw-up', function () {
290        var $this = $('.rc-active');
291        $this.animate({'top': '270px'}).removeClass('rc-active');
292        $this.prev().animate({'top': '0'}).addClass('rc-active');
293        $('.rw-down').css('display', 'block');
294        if (!$this.prev().prev().hasClass('reminder-content')) {
295            $(this).hide();
296        } else {
297            $(this).show();
298        }
299        $('#popup_reminder_title').text($this.prev().data('type') + ' Approaching');
300        $("#popup_reminder_reschedule_dialog").hide();
301    });
302
303    $(document).on('keypress', '#popup_reminder_newTasksDialog', function (e) {
304        var $btn;
305        if (e.keyCode == 38) {
306            // up key pressed
307            $btn = $(this).find('span.rw-up.up-btn');
308        } else if (e.keyCode == 40) {
309            // down key pressed
310            $btn = $(this).find('span.rw-down.down-btn');
311        }
312
313        if ($btn && $btn.is(':visible')) {
314            $btn.click();
315        }
316
317        return false;
318    });
319
320    $(document).on('mouseenter', '#popup_reminder_newTasksDialog', function () {
321        $(this).focus();
322    });
323
324    $(document).on('mouseleave', '#popup_reminder_newTasksDialog', function () {
325        $(this).blur();
326    });
327
328    // Firefox scroll detection
329    $(document).on('DOMMouseScroll', '#popup_reminder_newTasksDialog', function (e) {
330        var $btn;
331        if (e.originalEvent.detail > 0) {
332            //scroll down
333            $btn = $(this).find('span.rw-down.down-btn');
334        } else {
335            //scroll up
336            $btn = $(this).find('span.rw-up.up-btn');
337        }
338
339        if ($btn && $btn.is(':visible')) {
340            $btn.click();
341        }
342
343        //prevent page fom scrolling
344        return false;
345    });
346
347    //IE, Opera, Safari
348    $(document).on('mousewheel', '#popup_reminder_newTasksDialog', function (e) {
349        var $btn;
350        if (e.originalEvent.wheelDelta < 0) {
351            //scroll down
352            $btn = $(this).find('span.rw-down.down-btn');
353        } else {
354            //scroll up
355            $btn = $(this).find('span.rw-up.up-btn');
356        }
357
358        if ($btn && $btn.is(':visible')) {
359            $btn.click();
360        }
361
362        //prevent page fom scrolling
363        return false;
364    });
365
366    $(document).on('click', '#popup_reminder_dismiss_all', function () {
367        var ids = [];
368        $('.reminder-content').each(function () {
369            ids.push($(this).data('tid'));
370        });
371
372        $.post('/reminders/markTaskCompleted', {tids: ids}, hideTaskReminderPopup);
373    });
374
375    $(document).on('change', '#popup_reminder_enddate_input', function () {
376        $('#popup_reminder_enddate_text').html($(this).val());
377        $('#popup_reminder_enddate_error').fadeOut(500)
378    });
379
380    var popup_reminder_enddate_input;
381    var popupReminderRescheduleTaskTimes = {};
382    $(document).on('click', '#popup_reminder_reschedule', function () {
383
384        var walkMeWidth = 43;
385        if ((typeof $('#walkme-player').css('width') !== 'undefined') &&
386            $('#walkme-player').css('width').indexOf('px') !== -1) {
387            walkMeWidth = parseInt($('#walkme-player').css('width'));
388        }
389        walkMeWidth += 2;
390        var datePickerWidth = 232;
391        if ($('#popup_reminder_reschedule_dialog:first').is(':hidden')) {
392            var eventTime = $('.rc-active').find('.time_val').html();
393            var recID = $('.rc-active').data('tid');
394
395            var data = eventTime.split(/[\s:]+/);
396
397            var hours = parseInt(data[0]);
398            var minutes = parseInt(data[1]);
399
400            $('#popup_reminder_reschedule_button').data('rid', recID);
401
402            if (! popupReminderRescheduleTaskStartObj) {
403                popupReminderRescheduleTaskStartObj = $.DateParser($('#popup_reminder_startdate_input'), {
404                    useTimePicker: true,
405                    actionButton: $('#popup_reminder_reschedule_button'),
406                    defaultValue: '',
407                    extraClass: 'popupReminderTaskRescheduleStart',
408                    callbackFunction: function() {
409                        var timeStart     = Date.parse($('#popup_reminder_startdate_input').val());
410                        var endDateString = '';
411
412                        if (! popupReminderRescheduleTaskSnoozeDD) {
413                            $('#popup_reminder_snooze_select').val('0');
414                        }
415                        popupReminderRescheduleTaskSnoozeDD = false;
416
417                        endDate = new Date(timeStart + popupReminderRescheduleTaskDuration);
418                        endDateString += ('0' + (endDate.getMonth() + 1)).slice(-2) + '/' + ('0' + endDate.getDate()).slice(-2) + '/' + endDate.getFullYear() + ' ';
419                        endDateString += endDate.toLocaleString('en-US', {hour: 'numeric', minute: 'numeric', hour12: true});
420                        $('#popup_reminder_enddate_input').datetimepicker('setDate', endDateString.toLowerCase());
421                    },
422                    dialogPosition: {
423                        my: "top",
424                        at: "top",
425                        of: $('#popup_reminder_reschedule_dialog')
426                    }
427                });
428                popupReminderRescheduleTaskStartObj.init({
429                    beforeShow: function(input) {
430                        var currentInput = $(input);
431                        setTimeout(function () {
432                            var div = $('#ui-datepicker-div');
433                            if (div.is(':visible')) {
434                                var actualInput = currentInput.parents('.ui-pnotify');
435                                div.css('top', actualInput.position().top);
436                                if ((parseInt(div.css('left')) + datePickerWidth) > $(window).width()) {
437                                    div.css('left', parseInt($(window).width()) 
437- datePickerWidth - walkMeWidth);
438                                }
439                                $('#ui-datepicker-div .ui-datepicker-close').focus();
440                            }
441                            $(document).on('keydown.closeDatepickerEsc', function (event) {
442                                if (event.keyCode === 27) {
443                                    self.element.datetimepicker('hide');
444                                    $(this).off('keydown.closeDatepickerEsc');
445                                    event.stopPropagation();
446                                }
447                            });
448                        }, 200);
449                    }
450                });
451            }
452
453            popup_reminder_enddate_input = $('#popup_reminder_enddate_input').datetimepicker({
454                showAnim: "fadeIn",
455                dateFormat: "mm/dd/yy",
456                onSelect: function (dateText) {
457                    $('#popup_reminder_enddate_text').html(dateText);
458                },
459                buttonImage: "/images/icons/date.png",
460                buttonImageOnly: true,
461                showOn: "both",
462                ampm: true,
463                timeFormat: "h:mm tt",
464                separator: ' ',
465                hourGrid: 6,
466                stepMinute: 1,
467                minuteGrid: 10,
468                beforeShow: function (input) {
469                    var currentInput = $(input);
470                    setTimeout(function () {
471                        var div = $('#ui-datepicker-div');
472                        if (div.is(':visible')) {
473                            var actualInput = currentInput.parents('.ui-pnotify');
474                            div.css('top', actualInput.position().top);
475                            // if part of datepicker window is hidden
476                            if ((parseInt(div.css('left')) + datePickerWidth) > $(window).width()) {
477                                div.css('left', parseInt($(window).width()) - datePickerWidth - walkMeWidth);
478                            }
479                            $('#ui-datepicker-div .ui-datepicker-close').focus();
480                        }
481                        $(document).on('keydown.closeDatepickerEsc', function (event) {
482                            if (event.keyCode === 27) {
483                                self.element.datetimepicker('hide');
484                                $(this).off('keydown.closeDatepickerEsc');
485                                event.stopPropagation();
486                            }
487                        });
488                    }, 200);
489                },
490                onClose: function () {
491                    var timeStart = Date.parse($('#popup_reminder_startdate_input').val());
492                    var timeEnd   = Date.parse($('#popup_reminder_enddate_input').val());
493
494                    if (timeStart && timeEnd) {
495                        if (timeStart >= timeEnd) {
496                            var errorContainer = $('#popup_reminder_enddate_error');
497                            var str = 'End Time needs to be after the Start Time';
498                            errorContainer.hide();
499                            errorContainer.find('.text').text(htmlSpecialCharsJS(str)).css({color: 'red'});
500                            errorContainer.fadeIn(500);
501                            $('#popup_reminder_reschedule_button').button('disable');
502                        } else {
503                            rescheduleDuration = timeEnd - timeStart;
504                            $('#popup_reminder_enddate_error').fadeOut(500)
505                            $('#popup_reminder_reschedule_button').button('enable');
506                        }
507                    } else {
508                        $('#popup_reminder_reschedule_button').button('disable');
509                        if (! timeEnd) {
510                            $('#popup_reminder_reschedule_button').button('enable');
511                        }
512                    }
513                }
514            });
515
516            $.ajax({
517                url: '/calendar/getTimeStamp/' + recID,
518                dataType: 'json',
519                success: function (result) {
520                    if (result) {
521                        if (result.start && result.end) {
522                            var taskdateStart = new Date(result.start);
523                            var taskdateEnd = new Date(result.end);
524                            popupReminderRescheduleTaskDuration = taskdateEnd.getTime() - taskdateStart.getTime();
525                        }
526                        popupReminderRescheduleTaskTimes = result;
527                        if ($('#popup_reminder_disable_past_reschedule') && $('#popup_reminder_disable_past_reschedule').val()) {
528                            $('#popup_reminder_enddate_input').datetimepicker('option', 'minDate', 0);
529                        }
530                        var pushForward = parseInt(getCookie('popupReminderTaskPushForward'));
531                        pushForward = pushForward ? pushForward : '30';
532                        $('#popup_reminder_snooze_select').val(pushForward).change();
533
534                        $('#popup_reminder_reschedule_dialog').slideDown();
535                    }
536                }
537            });
538        } else {
539            $('#popup_reminder_reschedule_dialog').slideUp();
540            $('#popup_reminder_enddate_input').val('');
541            $('#popup_reminder_enddate_text').html('Loading...');
542            popupReminderRescheduleTaskStartObj.reset();
543            popupReminderRescheduleTaskTimes = {};
544            popupReminderRescheduleTaskSnoozeDD = false;
545        }
546    });
547
548    $(document).on('change', '#popup_reminder_snooze_select', function () {
549        var snoozeTime = $(this).val();
550        if (snoozeTime !== 0) {
551            setCookie('popupReminderTaskPushForward', snoozeTime * 1);
552            snoozeTime = $(this).val() * 60 * 1000;
553            var startDate = Date.parse(popupReminderRescheduleTaskTimes.start);
554            var adjustedDate = new Date(startDate + snoozeTime);
555            var adjustedDateString = ('0' + (adjustedDate.getMonth() + 1)).slice(-2) + '/' + ('0' + adjustedDate.getDate()).slice(-2) + '/' + adjustedDate.getFullYear() + ' ';
556            adjustedDateString += adjustedDate.toLocaleString('en-US', {hour: 'numeric', minute: 'numeric', hour12: true});
557            popupReminderRescheduleTaskStartObj.setParserDate(adjustedDateString.toLowerCase());
558            popupReminderRescheduleTaskSnoozeDD = true;
559        } else {
560            popupReminderRescheduleTaskStartObj.setParserDate(popupReminderRescheduleTaskTimes.start.toLowerCase());
561        }
562    });
563
564    $(document).on('click', '#popup_reminder_reschedule_button', function () {
565        var rescheduleError = false;
566        var dateTimeStart   = $('#popup_reminder_startdate_input').val();
567        var dateTimeEnd     = $('#popup_reminder_enddate_input').val();
568        if (! dateTimeStart || ! dateTimeEnd) {
569            rescheduleError = true;
570            alertDialog('Enter any date please', 'Error');
571        }
572
573        // check start/end time
574        var dateS = moment(dateTimeStart, 'MM-DD-YYYY hma');
575        var dateE = moment(dateTimeEnd, 'MM-DD-YYYY hma');
576        if (dateS.isSame(dateE) || dateE.isBefore(dateS)) {
577            createErrorNotification('Add Task', 'End Time needs to be after Start Time.');
578
579            return false;
580        }
581
582        if (! rescheduleError) {
583            $.ajax({
584                type: 'post',
585                url: '/calendar/reschedule',
586                dataType: 'json',
587                data: {
588                    id: $(this).data('rid'),
589                    dateTimeStart: dateTimeStart,
590                    dateTimeEnd: dateTimeEnd
591                },
592                success: function (response) {
593                    if (response.status == 'success') {
594                        hideTaskReminderPopup();
595                    } else {
596                        alertDialog(response.status, 'Error');
597                    }
598                }
599            });
600        }
601    });
602
603    $(window).scroll(function () {
604        if ($('#popup_reminder_newTasksDialog').length > 0) {
605            if (( $(window).scrollTop() + 10 ) > parseInt($('#popup_reminder_newTasksDialog').css('top'))) {
606                $('#popup_reminder_newTasksDialog').css('top', '10px');
607            } else {
608                if ($('#content').length > 0) {
609                    $('#popup_reminder_newTasksDialog').css('top', $('#content').offset().top);
610                }
611            }
612        }
613    });
614
615    $(document).on('click', '.reminder-close', function() {
616        var ids = [];
617        $('.reminder-content').each(function () {
618            ids.push($(this).data('tid'));
619        });
620
621        $.post('/reminders/markTaskCompleted', {tids: ids}, hideTaskReminderPopup);
622    });
623
624    $(document).on('change', '#popup_reminder_settings_task, #popup_reminder_settings_appointment', function () {
625        var $select = $(this).parent().find('.useChosen');
626
627        if ($(this).prop('checked') && !$select.val()) {
628            $select.val($(this).data('default'));
629        }
630        $select.prop('disabled', !$(this).prop('checked'));
631
632        $("#popup_reminder_settings_button").button({disabled: false});
633    });
634
635    $(document).on('change', '#popup_reminder_settings_task_select, #popup_reminder_settings_appointment_select', function () {
636        $("#popup_reminder_settings_button").button({disabled: false});
637    });
638
639    $(document).on('click', '#popup_reminder_settings', function () {
640        $.getJSON('/settings/getNotifications', function (response) {
641            if ($('#popup_reminder_settings_dialog:first').is(':hidden')) {
642
643                $('#popup_reminder_settings_task_select').val(response.task)
644                .prop('disabled', response.task == 0);
645                $('#popup_reminder_settings_appointment_select').val(response.appointment)
646                .prop('disabled', response.appointment == 0);
647
648                $('#popup_reminder_settings_task').prop('checked', response.task != 0);
649                $('#popup_reminder_settings_appointment').prop('checked', response.appointment != 0);
650
651                $('.chosen-search', '#popup_reminder_settings_dialog').hide();
652                $("#popup_reminder_settings_button").button({disabled: true});
653
654                $('#popup_reminder_settings_dialog').slideDown();
655            } else {
656                $('#popup_reminder_settings_dialog').slideUp();
657            }
658        });
659    });
660
661    $(document).on('click', '#popup_reminder_settings_button', function () {
662        var taskReminder = $('#popup_reminder_settings_task_select').val();
663        var appointmentReminder = $('#popup_reminder_settings_appointment_select').val();
664
665        taskReminder = $('#popup_reminder_settings_task').prop('checked') ? taskReminder : 0;
666        appointmentReminder = $('#popup_reminder_settings_appointment').prop('checked') ? appointmentReminder : 0;
667
668        //save settings
669        $.post('/settings/saveNotifications', {
670            taskReminder: taskReminder,
671            appointmentReminder: appointmentReminder
672        }, function (result) {
673            window.reminderSettings = null;
674
675            if (result == 'OK') {
676                $('#popup_reminder_settings_dialog').slideUp();
677            } else {
678                alertDialog('Unable to save notifications.', 'Error');
679            }
680        });
681    });
682
683    $(document).on('click', '.popup-reminder-cancel-button', function() {
684        $($(this).data('button')).click();
685    });
686
687    /** End Task Reminder Functions **/
688
689    // used for pnotify, check initNotifications.js for details
690    $(window).bind('storage', function (e) {
691        e = e.originalEvent;
692
693        if (e?.key?.indexOf('_notification_') != -1) {
694            // if it was removed
695            if ((e.newValue == null || e.newValue == '') && e.oldValue != '') {
696                $('.' + e.oldValue).remove();
697            }
698        }
699    });
700
701    $('.default-tooltip').tooltip({
702        content: function () {
703            var element = $(this);
704            return element.attr('title');
705        },
706        position: {
707            my: "left bottom-5",
708            collision: "flip"
709        },
710        show: {duration: 100},
711        hide: {duration: 0},
712        track: true
713    });
714
715	setTimeout(function(){$('#login_error').fadeOut(500)}, 8000);
716
717	//menu
718	$('#nav > li').bind('mouseover', jsddm_open);
719	$('#nav > li').bind('mouseout',  jsddm_timer);
720
721	$('.current_user_name').click(function(e) {
722		e.stopImmediatePropagation();
723		$('.dropdown-user').toggle();
724
725        if ($('.dropdown-user').is(':visible')) {
726            loadUserRemainingCredits()
727        }
728	});
729
730    function loadUserRemainingCredits() {
731        var $container = $('.current_user_name').find('.user-remaining-credits')
732
733        $.get('/user/remaining/credits').then(function(result) {
734            if (result && result.length) {
735                $container.html(result)
736            } else {
737                $container.html('')
738            }
739        })
740    }
741
742	$(document).click(function() {
743		$('.dropdown-user').hide();
744	});
745
746
747	if ($("#leadAC").length!==0)
748	{
749		var search_user = $('#ddviewuser option:selected').val();
750        var search_by_value = $('select[name=searchby]').val();
751        var min_length = ($('select[name=searchby] option:selected').text().toLowerCase()=='state') ? 2 : 3;
752		leadSearchAutocomplete($("#leadAC"), search_by_value, search_user, min_length);
753
754		$(document).on('change', 'select[name=searchby]', function() {
755            min_length = ($('option:selected',$(this)).text().toLowerCase()=='state') ? 2 : 3;
756			search_by_value = $(this).val();
757			leadSearchAutocomplete($("#leadAC"), search_by_value, search_user, min_length);
758
759		});
760
761		$(document).on('change', '#ddviewuser', function() {
762			search_user = $('#ddviewuser option:selected').val();
763			leadSearchAutocomplete($("#leadAC"), search_by_value, search_user, min_length);
764		});
765	}
766
767	//FORGOT PASSWORD PAGES
768	$('#btnReset, #continue_password_reset').button();
769
770	$('.btnForgot').button({
771		icons: {
772			secondary: 'ui-icon-check'
773		}
774	}).on('click', function(e) {
775		e.preventDefault();
776
777		var $input = $(this).closest('form').find('input[type=text]');
778
779		if ($input.val().trim().length > 0) {
780			$(this).closest('form').submit();
781			$input.css({'border-color' : '#aaa'});
782		} else {
783			$input.css({'border-color' : 'red'});
784		}
785	});
786	$(document).on('click', '#continue_password_reset', function() {
787		if ($(this).attr('goto')) {
788			window.location = $(this).attr('goto');
789		} else {
790			window.location = '/';
791		}
792	});
793	$(document).on('click', "#btnReset", function(e) {
794		e.preventDefault();
795
796		if ($('#elPass1').val() != $('#elPass2').val()) {
797			alert('Passwords do not match');
798		} else if ($('#elPass1').val().length == 0) {
799			alert('Please enter a new password');
800		} else if ($('#elPass1').val().charAt(0).trim() == "") {
801			alert('Password can not begin with an empty space');
802		} else {
803			$('#frmReset').submit();
804		}
805	});
806
807	//leads.php
808	//unassigned leads
809	$('#selectall').click(function() {
810		var status = this.checked;
811		$("[name='ids[]']").each(function() { this.checked = status; });
812	});
813
814	$("#reassign").click(function() {
815		if ($('#enableassignto').is(':checked') || $('#enablestatus').is(':checked')) {
816			var txtMsg = '';
817
818			if ( ! $('#enableassignto').is(':checked') && $('#enablestatus').is(':checked')) {
819				txtMsg = 'Are you sure you want to change the status of these leads?';
820			} else {
821				txtMsg = 'Are you sure you want to reassign these leads?';
822			}
823
824			if (txtMsg.length) {
825				if (confirm(txtMsg)) {
826					$("#frmleads").attr("action", "reassign/").attr("method", "POST").submit();
827					return true;
828		        }
829		    }
830		}
831
832        return false;
833	});
834
835	$("#unassign, #unassignsingle").click(function() {
836		if (confirm('Are you sure you want to unassign these leads?')) {
837			$("#frmleads").attr("action", "reassign/").attr("method", "POST").submit();
838			return true;
839        }
840
841        return false;
842	});
843
844	$('.jqdatepicker').datepicker({
845		inline: true,
846		showOn: 'both',
847		buttonImage: '/images/icons/date.png',
848		buttonImageOnly: true
849	});
850
851	// Notifications header
852	var notificationsOpen                = false;
853	var notificationsAutoRefreshInterval = false;
854	if ($('.nb-content') && $('.nb-content').length) {
855
856		$('.notifications-bubble').click(function() {
857
858			$('.notifications-bubble .nb-dropdown').show();
859
860
861			$(".notifications-bubble .nb-dropdown").mouseup(function() {
862			    return false
863			});
864
865			$(document).mouseup(function(e) {
866			    if($(e.target).parents(".notifications-bubble .nb-dropdown").length==0 && !$(e.target).is(".notifications-bubble .nb-dropdown")) {
867			    	notificationsOpen = false;
868			    	if ($('.nb-dropdown').css('display') == 'block') {
869				    	$(".notifications-bubble .nb-dropdown").hide();
870				    	$('.nb-content ul').html('<li class="nb-not">No Notifications</li>');
871						$('.notifications-bubble .nb-count').html(0);
872						$('.notifications-bubble .nb-count').hide();
873					}
874			    }
875			});
876
877            $.ajax({
878                type: 'post',
879                url: '/notifications/markread',
880                success: function(result) {
881                    $('.notifications-bubble .nb-count').html(0);
882                    $('.notifications-bubble .nb-count').hide();
883                }
884            });
885		});
886
887        new PeriodicTask(15, function(){
888            if ( !document.hidden ) {
889                return reloadUserNotifications();
890            }
891
892            return $.when();
893        });
894	}
895
896	function reloadUserNotifications() {
897		return $.ajax({
898			type: 'get',
899			url: '/notifications/getAllNotifications',
900			dataType: 'json',
901			success: function(result) {
902				if ($('.nb-dropdown').css('display') == 'none') {
903					if (result) {
904						var toAppend = '';
905						var total    = 0;
906						var limit    = 5;
907
908						$.each(result[0], function(i, item) {
909							// remove double quotes
910							i = i.replace(/"/g, "");
911
912							// convert to integer
913							i = parseInt(i);
914
915							toAppend += '<li ' + (limit > 0 ? '' : 'style="display: none;"') + 'class="notification-item" id="notification_item_'+i+'" notification-type-int="'+item['TypeInt']+'" notification-time="'+item['DateCompare']+'" notification-type="'+item['Type']+'">' +
916											'<span class="notification-text">'+convertPlaceholdersToEmoji(item['DisplayText'])+'</span>' +
917											'<span class="notification-date">'+item['Date']+'</span>' +
918										'</li>';
919
920							limit--;
921							total++;
922						});
923
924						$('.nb-content ul').html(toAppend);
925						$('.notifications-bubble .nb-count').html(result[1]);
926
927						if (result[1] > 0) {
928							$('.notifications-bubble .nb-count').show();
929						} else {
930							$('.nb-content ul').html('<li class="nb-not">No Notifications</li>');
931							$('.notifications-bubble .nb-count').hide();
932						}
933					} else {
934						$('.nb-content ul').html('<li class="nb-not">No Notifications</li>');
935						$('.notifications-bubble .nb-count').html(0);
936						$('.notifications-bubble .nb-count').hide();
937					}
938				}
939			}
940		});
941	}
942
943	// Notifications widget
944	$(function() {
945		setInterval(function() {
946			reloadCustomAlerts();
947		}, 1000);
948	});
949
950	function reloadCustomAlerts() {
951		$('.custom-alerts-containers').each(function() {
952			$alertsWidget   = $(this);
953			var supportedTypes = $alertsWidget.find('.supportedTypes').val();
954			var alertLimit     = parseInt($alertsWidget.find('.alertLimit').val());
955			alertLimit         = isNaN(alertLimit) ? 0 : alertLimit;
956			supportedTypes     = supportedTypes && supportedTypes.length ? supportedTypes.split(",") : [];
957
958			// see if notifications is present
959			var toAppend = '';
960			var count    = 0;
961
962			$('#notifications_content .notification-item').each(function() {
963				var notificationID = $(this).attr('id').split("_").pop();
964
965				if ($alertsWidget.find('.widget_notification_item_'+notificationID) && $alertsWidget.find('.widget_notification_item_'+notificationID).length) {
966					// exists
967				} else {
968					var notificationDate    = $(this).find('.notification-date').text();
969					var notificationText    = $(this).find('.notification-text').html();
970					var notificationType    = $(this).attr('notification-type');
971					var notificationTypeInt = $(this).attr('notification-type-int');
972					var notificationDateInt = parseInt($(this).attr('notification-time'));
973
974					if ($.inArray(notificationTypeInt, supportedTypes) >= 0) {
975						var latestDate = parseInt($alertsWidget.find('table tr:first-child').attr('alert-date-int'));
976
977						// now check if date is latest
978						if (isNaN(latestDate) || notificationDateInt > latestDate) {
979							// new notification
980							toAppend += '<tr class="widget_notification_item_'+notificationID+' new-notification" alert-date-int="'+notificationDateInt+'">' +
981											'<td class="custom-alert-icon">';
982							if (notificationTypeInt == '1') {
983								toAppend += '<img src="images/change.png" alt="'+notificationType+'">';
984							} else if (notificationTypeInt == '2') {
985								toAppend += '<img src="images/note.png" alt="'+notificationType+'">';
986							} else if (notificationTypeInt == '3') {
987								toAppend += '<img src="images/memo.png" alt="'+notificationType+'">';
988							} else if (notificationTypeInt == '4') {
989								toAppend += '<span class="glyphicon glyphicon-envelope" title="'+notificationType+'"></span>';
990							} else if (notificationTypeInt == '5') {
991								toAppend += '<img src="images/messages-icon.png" alt="'+notificationType+'" width="16">';
992							}
993
994							toAppend += 	'</td>' +
995											'<td class="custom-alert-text">' +
996												'<span class="custom-alert-type">' + notificationType + '</span>' +
997												'<span class="custom-alert-content">' + convertPlaceholdersToEmoji(notificationText) + '</span>' +
998											'</td>' +
999											'<td class="custom-alert-date">' + notificationDate + '</td>' +
1000										'</tr>';
1001
1002							count++;
1003						}
1004					}
1005				}
1006			});
1007
1008			if (count > 0) {
1009				// remove last alerts to add new ones
1010				var removeCount = parseInt($alertsWidget.find('table tr').length) + count;
1011
1012				if (removeCount > alertLimit) {
1013					removeCount = removeCount - alertLimit;
1014				} else {
1015					removeCount = 0;
1016				}
1017
1018				if (removeCount > 0) {
1019					$alertsWidget.find('table tr').slice("-" + count).fadeOut(500)
1020				}
1021
1022				$alertsWidget.find('.no_widget_alerts').remove();
1023				$alertsWidget.find('table tbody').html(toAppend + $alertsWidget.find('table tbody').html());
1024				$alertsWidget.find('.new-notification').fadeIn(2000, function() {
1025					$this = $(this);
1026					for ( var i = 1; i < 3; i++ ) {
1027						$this.fadeTo(500, 0.1).fadeTo(1000, 1.0);
1028					}
1029					setTimeout(function() {
1030						$('.new-notification .custom-alert-icon').css('border-left-color', 'red');
1031					}, 3000);
1032					setTimeout(function() {
1033						$('.new-notification .custom-alert-icon').css('border-left-color', 'transparent');
1034						$('.new-notification').removeClass('new-notification');
1035					}, 10*1000);
1036				});
1037			}
1038		});
1039	}
1040
1041    $('#content').on('change', "select[name='dashboarduid'],select[name='dashboardgroupid'],input[name='excludeUnassigned']", function (e) {
1042        var dropdown   = $(this),
1043            container  = dropdown.closest('.dashboardview'),
1044            currentTab = container.closest('.ui-tabs-panel'),
1045            href       = $('#tabs-dashboard').find('.ui-tabs-nav').find('.ui-state-active a').attr('href'),
1046            userID     = container.find("select[name='dashboarduid']").val(),
1047            groupID    = container.find("select[name='dashboardgroupid']").val(),
1048            excludeUnassigned  = $(this).parents('.dashboardview').find("input[name='excludeUnassigned']").is(':checked') ? 1 : 0;
1049
1050        if($(this).is(':checkbox, [name="excludeUnassigned"]')) {
1051            excludeUnassigned = $(this).is(':checked') ? 1 : 0;
1052        }
1053
1054        /*
1055         * Paints the loading status on the tab
1056         */
1057        currentTab.css('height', currentTab.css("height"));
1058        currentTab.html('' +
1059            '<div style="text-align:center;margin-top:65px;">' +
1060            '<img src="/images/ajax-loader.gif" />' +
1061            '<br><br>Refreshing Content...</div>' +
1062            '');
1063
1064        /*
1065         * Refreshes the tab content
1066         */
1067        currentTab.load(
1068            href +
1069            '/?dashboarduid=' + userID +
1070            '&dashboardgroupid=' + groupID +
1071            '&excludeUnassigned=' + excludeUnassigned
1072        );
1073    });
1074
1075    $(document).on('mouseenter mouseover', '.export-to-calendar', function() {
1076        if ($(document).height() < ($(this).offset().top + $(this).outerHeight(true) + $(this).find('.calendars').outerHeight(true))) {
1077            $(this).find('.calendars').css({
1078                'top' : 'initial',
1079                'bottom': '25px'
1080            });
1081        } else {
1082            $(this).find('.calendars').css({
1083                'top' : '23px',
1084                'bottom': 'initial'
1085            });
1086        }
1087
1088        $(this).find('.calendars').show();
1089    });
1090
1091    $(document).on('mouseleave mouseexit', '.export-to-calendar', function() {
1092        $(this).find('.calendars').hide();
1093    });
1094
1095    initUserAutocompleteDropDowns();
1096
1097    $(document).on('click', '.preview-postcard-html-template', function () {
1098        var templateId = $(this).data('templateid');
1099        $.get('/manage/postcard/templates/' + templateId + '/preview', function(data) {
1100            var dialog = $('<div></div>').html(data).dialog({
1101                title: 'Preview',
1102                modal: true,
1103                width: 'auto',
1104                height: 'auto',
1105                position: { my: 'center', at: 'center', of: window }
1106            });
1107            dialog.dialog('open');
1108        });
1109    });
1110
1111    $(document).on('click', '.payment-attached-to-note', function () {
1112        var $obj      = $(this);
1113        var paymentId = parseInt($obj.data('id'));
1114
1115        if (isNaN(paymentId) || ! paymentId) {
1116            createErrorNotification('Error', 'Payment record not found.');
1117
1118            return false;
1119        }
1120
1121        var paymentDetailsDialog = new PaymentDetailsDialog(createDialog({autoOpen: false})());
1122        paymentDetailsDialog.setPaymentId(paymentId);
1123        paymentDetailsDialog.setBaseUrl('/payment/' + paymentId);
1124        paymentDetailsDialog.setSyncable(false);
1125        paymentDetailsDialog.open();
1126    });
1127
1128    $(document).on('click', '.invoice-attached-to-note', function () {
1129        var invoiceId = $(this).data('invoice');
1130        var leadId = $(this).data('lead');
1131
1132        if ($('.view-invoice-dialog').length == 0) {
1133            $('body').append($('<div class="view-invoice-dialog"></div>'));
1134        }
1135
1136        $('.view-invoice-dialog').dialog({
1137            dialogClass: 'dialog-container form-container',
1138            autoOpen: true,
1139            modal: true,
1140            title: 'View Invoice',
1141            width: 800,
1142            position: {my: 'center center', at: 'center center'},
1143            open: function () {
1144                $(this)
1145                .html('<div class="text-center"><img src="/images/ajax-loader.gif" alt="Loading..." /></div>')
1146                .load('/lead/' + leadId + '/invoices/view/' + invoiceId, function () {
1147                    $('.view-invoice-dialog').dialog({position: {my: 'center', at: 'center', of: window}});
1148                    $('.invoice-send', '.view-invoice-dialog').hide();
1149                });
1150            }
1151        });
1152    });
1153});
1154
1155document.onclick = jsddm_close;
1156
1157/**
1158 * Scroll to the given element and focuses it
1159 *
1160 * @param {object} $element
1161 * @param {function} callback
1162 */
1163window.scrollToAndFocus = function scrollToAndFocus($element, callback) {
1164    $('html, body').animate({
1165        scrollTop: $element.offset().top - 100
1166    }, 1500, function(){
1167        if ($element.hasClass('chosen-container')) {
1168            $element.siblings('select.useChosen').trigger('chosen:activate');
1169        } else {
1170            $element.focus();
1171        }
1172
1173        if (typeof callback === 'function') {
1174            callback();
1175        }
1176    });
1177};
1178
1179/**
1180 * Custom function used for retrieving value of get paramater from URL
1181 */
1182window.getSearch = function(variable) {
1183	var parsedSearch;
1184	parsedSearch = window.parsedSearch || (function() {
1185		var match, re, ret;
1186		re = /\??(.*?)=([^\&]*)&?/gi;
1187		ret = {};
1188	    while (match = re.exec(document.location.search)) {
1189	    	ret[match[1]] = match[2];
1190	    }
1191	    return window.parsedSearch = ret;
1192	})();
1193	return parsedSearch[variable];
1194};
1195
1196/**
1197 * IE6-8 Compatibility support
1198 */
1199if (!Array.prototype.indexOf)
1200{
1201  Array.prototype.indexOf = function(elt /*, from*/)
1202  {
1203    var len = this.length;
1204
1205    var from = Number(arguments[1]) || 0;
1206    from = (from < 0)
1207         ? Math.ceil(from)
1208         : Math.floor(from);
1209    if (from < 0)
1210      from += len;
1211
1212    for (; from < len; from++)
1213    {
1214      if (from in this &&
1215          this[from] === elt)
1216        return from;
1217    }
1218    return -1;
1219  };
1220}
1221
1222// Format number into dollar value, optional cents and currency
1223function formatCurrency(num, decimals, currency) {
1224	num = num.toString().replace(/\$|\,/g, '');
1225	if (isNaN(num)) num = "0";
1226	sign = (num == (num = Math.abs(num)));
1227	num = Math.floor(num * 100 + 0.50000000001);
1228	cents = num % 100;
1229	num = Math.floor(num / 100).toString();
1230	if (cents < 10) cents = "0" + cents;
1231	for (var i = 0; i < Math.floor((num.length - (1 + i)) / 3); i++)
1232	num = num.substring(0, num.length - (4 * i + 3)) + ',' + num.substring(num.length - (4 * i + 3));
1233    currency = formatCurrencySign(currency);
1234
1235	return (((sign) ? '' : '-') + currency + num + (decimals ? '.' + cents : '') );
1236}
1237
1238// Formats currency into corresponding sign
1239function formatCurrencySign(currency) {
1240    if (currency === undefined || currency === '' || currency === 'USD') {
1241        return '$';
1242    } else if (currency === 'EUR') {
1243        return '€';
1244    } else if (currency === 'GBP') {
1245        return '£'
1246    } else {
1247        return currency;
1248    }
1249}
1250
1251/**
1252 * Format number: add commas in thousands, shorten and round if required.
1253 *     - 9.999      => '9.999'
1254 *     - 1999.99    => '1,999.99'
1255 *     - '1,999.99' => '1,999.99'
1256 *     - '199999'   => '199,999'
1257 * with shorten enabled (& doRound: false):
1258 *     - 200000 => '200K'
1259 *     - 199999 => '199,9K'
1260 * with doRound enabled:
1261 *     - 199500 => '200K'
1262 *     - 199499 => '199K'
1263 *     - 9.99   => '10K' // TODO review
1264 *
1265 * Does not work with float with more than 3 decimals: '99999.9999' => "99,999.9,999"
1266 *
1267 * @param {string|int|float} x       Number to format, could be 20000
1268 * @param {boolean}          shorten Apply shorten and add suffix 'K', 'M' or 'B'
1269 * @param {boolean}          doRound Apply round, only when shortening (default true), not work with decimals
1270 *
1271 * @return {string}
1272  */
1273function numberWithCommas(x, shorten, doRound) {
1274    if (doRound === undefined) {
1275        doRound = true;
1276    }
1277
1278	// get rid of all non-numeric or dot characters
1279	x = x.toString().replace(/[^\d\.]/g, "");
1280	if (x.length === 0) x = 0;
1281
1282    // add commas in thousands: https://regex101.com/r/D4xSqZ/1
1283    num = x.toString().replace(/\B(?=(?:\d{3})+(?!\d))/g, ",");
1284
1285    if (shorten)
1286
1287   		num =   (doRound
1288                    // extract first part of value, including first digit after ',' and round value
1289                    ? Math.round(num.substring(0, (num.indexOf(",")==-1?3:num.indexOf(","))+2 ).replace(",","."))
1290                    // extract first part of value, including first digit after ',', replace ',0' by empty string
1291                    : num.substring(0, (num.indexOf(",")==-1?3:num.indexOf(","))+2 ).replace(",0","")
1292                )
1293                + '' + (num.length<=3?"":(num.length<=7?"K":(num.length<=11?"M":"B")));
1294    return num;
1295}
1296
1297function numberWithDots(x, shorten, doRound) {
1298    return numberWithCommas(x, shorten, doRound).replace(/,/g, ".");
1299}
1300
1301// Get filename from path
1302function getFileName(path) {
1303	return path.match(/[-_\w]+[.][\w]+$/i)[0];
1304}
1305
1306var dataTableLengthMenu = [
1307    [10, 25, 50, 100, 250, 500, 1000],
1308    [10, 25, 50, 100, 250, 500, 1000]
1309];
1310
1311/**
1312 * Returns the parameter value if valid name is given.
1313 *
1314 * see @ http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values
1315 *
1316 * @param  {string} name
1317 * @param  {string} defaultValue
1318 * @return string empty string if parameter is not set or value of the parameter
1319 */
1320function getParameterByName(name, defaultValue)
1321{
1322    name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
1323    var regexS = "[\\?&]" + name + "=([^&#]*)";
1324    var regex = new RegExp(regexS);
1325    var results = regex.exec(window.location.search);
1326
1327    if(results == null) {
1328        if(defaultValue != undefined) {
1329            return defaultValue;
1330        } else {
1331            return '';
1332        }
1333    } else {
1334        return decodeURIComponent(results[1].replace(/\+/g, " "));
1335    }
1336}
1337
1338/**
1339 * Get tall the URL parameters from the current page
1340 * or optionally given url
1341 *
1342 * @param [search]
1343 * @returns {{}}
1344 */
1345function getAllParameters(search)
1346{
1347	var query = search || window.location.search,
1348		regex = /[?&;](.+?)=([^&;]*)/g,
1349		match,
1350		urlParams = {};
1351
1352	if (query) {
1353		while (match = regex.exec(query)) {
1354			urlParams[match[1]] = decodeURIComponent(match[2]);
1355		}
1356	}
1357
1358	return urlParams;
1359}
1360
1361/**
1362 * new Auto complete function
1363 */
1364window['leadSearchAutocomplete'] = function($field, search_by_value, uid, min_length) {
1365
1366    if (undefined===uid) uid = 0;
1367
1368    if (undefined===min_length) min_length = 3;
1369
1370    acImage = $("<img src='/images/ajax-loader.gif' width='16' height='16' />").insertAfter($field);
1371
1372    acImage.css({
1373        position: 'absolute',
1374        visibility: 'hidden',
1375        top: $field.position().top + 3,
1376        left: $field.position().left + $field.width() - 10,
1377        zIndex: 100
1378    });
1379
1380    $field.autocomplete({
1381        source: function(request, response) {
1382            $.ajax({
1383                context: acImage,
1384                type: 'post',
1385                url: '/search/lead',
1386                data: { term: request.term, searchby: search_by_value },
1387                dataType: 'json',
1388	    		beforeSend: function() {
1389                    acImage.css({visibility: 'visible'});
1390	    		},
1391                success: function(result) {
1392                    var count    = 0;
1393                    var dbaField = result[2].dbaField;
1394                    var total    = result[1];
1395
1396                    var items = $.map(result[0], function (item) {
1397                        var id = parseInt(item._id);
1398                        count++;
1399
1400                        var dba = function() {
1401                            if (dbaField != false && typeof item._source[dbaField] !== 'undefined') {
1402                                return item._source[dbaField][0] != '' ? item._source[dbaField][0] : '(blank)';
1403                            } else {
1404                                return '(blank)';
1405                            }
1406                        }();
1407
1408                        return {
1409                            id: id,
1410                            label: count + '. ' + dba,
1411                            value: dba
1412                        };
1413                    });
1414
1415                    if (total > 50) {
1416                        count++;
1417                        items.push({
1418                            id: '',
1419                            label: count + '. View All (' + total + ')',
1420                            value: request.term
1421                        });
1422                    }
1423
1424                    response(items);
1425                },
1426                complete: function () {
1427                    acImage.remove();
1428                }
1429            });
1430        },
1431        delay: 500,
1432        minLength: min_length,
1433        select: function(event, ui) {
1434            if(ui.item.id) {
1435                window.location = window.siteInfo.getUrl('/lead/view') + "/" + ui.item.id;
1436            } else {
1437                window.location = window.siteInfo.getUrl('/myleads') + "/?user1=0&searchby=" + search_by_value + "&term=" + ui.item.value;
1438            }
1439        }
1440    });
1441};
1442
1443/* get basename of a path*/
1444function baseName(str, removeExt)
1445{
1446   var base = new String(str).substring(str.lastIndexOf('\\') + 1);
1447   if(removeExt) {
1448		if(base.lastIndexOf(".") != -1) {
1449		   base = base.substring(0, base.lastIndexOf("."));
1450		}
1451   }
1452
1453   return base;
1454}
1455
1456/*Add leading zeros to number*/
1457function pad(number, length)
1458{
1459	var str = '' + number;
1460	while (str.length < length)
1461	{
1462		str = '0' + str;
1463	}
1464
1465	return str;
1466}
1467
1468/*
1469 * get time elapse between 2 timestamps  (format - xx:xx:xx)
1470 */
1471function getTimeElapsed(startTime, endTime, options)
1472{
1473	var difference = endTime - startTime;
1474
1475	var days = Math.floor(difference/1000/60/60/24);
1476	difference -= days*1000*60*60*24;
1477
1478	var hours = Math.floor(difference/1000/60/60);
1479	difference -= hours*1000*60*60;
1480
1481	var minutes = Math.floor(difference/1000/60);
1482	difference -= minutes*1000*60;
1483
1484	var seconds = Math.floor(difference/1000);
1485
1486	var output;
1487	var retDataOnly = typeof options != 'undefined' && typeof options['retDataOnly'] != 'undefined' && typeof options['retDataOnly'] != 'null' && options['retDataOnly'];
1488	var excludeDays = typeof options == 'undefined' || typeof options['bExcludeDays'] == 'undefined' || !options['bExcludeDays'];
1489
1490	if(retDataOnly) {
1491		output = {'seconds' : seconds, 'minutes' : minutes, 'hours' : hours, 'days' : days}
1492	} else {
1493		output = ((days && excludeDays) ? pad(days) + ' days ' : '') + pad(hours, 2) + ':' + pad(minutes, 2) + ':' + pad(seconds, 2);
1494	}
1495	return output;
1496}
1497
1498const defaultJQueryTooltip = {show: 100, hide: 0, track: true, position: { my: "left top-50"} };
1499
1500/**
1501 * Shortens a string to the specified maximum length.
1502 *
1503 * The suffix will be counted towards the maximum value
1504 *
1505 * @param {string} text - The string shorten
1506 * @param {number} maxLength - The maximum length of the string
1507 * @param {string} [suffix=''] - The string to append the end, default is three dots(...)
1508 * @returns {string} - The original string if the length is less than the allowed maximum, otherwise the shortened string + suffix
1509 */
1510function shortenString(text, maxLength, suffix)
1511{
1512	if ( text.length > maxLength )
1513	{
1514		suffix = !! suffix ? suffix : '...';
1515		text = text.substring(0, maxLength - suffix.length) + suffix;
1516	}
1517
1518	return text;
1519}
1520
1521window.zipCodeAutoComplete = function($object) {
1522	if ($object && $object.length) {
1523		$object.autocomplete({
1524			source: function(request, response) {
1525			    $.ajax({
1526			    	type: 'post',
1527			        url: "/ajax/zipcodeautocomplete/",
1528			        data: {
1529			            term: request.term
1530			        },
1531			        beforeSend: function() {
1532						var tempWidth   = $object.width();
1533						var tempHeight  = $object.height();
1534						var position    = $object.position();
1535
1536						$object.parent().append('<img forinput="'+$object.attr('name')+'" src="/images/ajax-loader-bars.gif" style="position: absolute; top: '+(position.top+4)+'px; left: '+(position.left+tempWidth+10)+'px;" />');
1537			        },
1538			        dataType: "json",
1539			        success: function(data) {
1540			        	$object.parent().find('img[forinput='+$object.attr('name')+']').remove();
1541
1542			            response($.map(data, function(item) {return item;}));
1543			        }
1544			    });
1545			},
1546			minLength: 3,
1547			select: function(event, ui) {
1548				if ($object.attr('zip-cities')) {
1549					var cityFields  = $object.attr('zip-cities');
1550					cityFields      = cityFields.split(",");
1551
1552					for (var city_i  = 0; city_i  < cityFields.length;  city_i++) {
1553						$cityObj = $('[name=lf_'+cityFields[city_i].trim()+']');
1554						if ($cityObj.is('select')) {
1555							$cityObj.find('option').each(function() {
1556								if($(this).text().toLowerCase() == ui.item.City.toLowerCase()) {
1557                                    $cityObj.val($(this).val());
1558
1559									return false;
1560								}
1561							});
1562						} else if ($cityObj.is('input') || $cityObj.is('textarea')) {
1563							$('[name=lf_'+cityFields[city_i].trim()+']').val(ui.item.City).change();
1564						}
1565					}
1566				}
1567
1568				if ($object.attr('zip-states')) {
1569					var stateFields = $object.attr('zip-states');
1570					stateFields     = stateFields.split(",");
1571
1572					for (var state_i = 0; state_i < stateFields.length; state_i++) {
1573						$stateObj = $('[name=lf_'+stateFields[state_i].trim()+']');
1574						if ($stateObj.is('select')) {
1575							$stateObj.find('option').each(function() {
1576								if($(this).text().toLowerCase() == ui.item.State.toLowerCase()) {
1577                                    $stateObj.val($(this).val());
1578
1579									return false;
1580								}
1581							});
1582						} else if ($stateObj.is('input') || $stateObj.is('textarea')) {
1583							$('[name=lf_'+stateFields[state_i].trim()+']').val(ui.item.State).change();
1584						}
1585					}
1586				}
1587
1588				if ($object.attr('zip-zipcodes')) {
1589					var zipFields   = $object.attr('zip-zipcodes');
1590					zipFields       = zipFields.split(",");
1591
1592					for (var zip_id  = 0; zip_id  < zipFields.length;   zip_id++) {
1593						$zipObj = $('[name=lf_'+zipFields[zip_id].trim()+']');
1594						if ($zipObj.is('select')) {
1595							$zipObj.find('option').each(function() {
1596								if($(this).text().toLowerCase() == ui.item.Zipcode.toLowerCase()) {
1597                                    $zipObj.val($(this).val());
1598
1599									return false;
1600								}
1601							});
1602						} else if ($zipObj.is('input') || $zipObj.is('textarea')) {
1603							$('[name=lf_'+zipFields[zip_id].trim()+']').val(ui.item.Zipcode).change();
1604						}
1605					}
1606				}
1607			}
1608		}).data("ui-autocomplete")._renderItem = function( ul, item ) {
1609			return $("<li></li>")
1610			.data("ui-autocomplete-item", item)
1611			.append("<a><b>" + item.Zipcode + "</b>, " + item.City + ", " + item.State)
1612			.appendTo(ul);
1613		};
1614	}
1615};
1616
1617/**
1618 * Bind predefined CSS class based masks to inputs
1619 * @param $scope - Parent element to restrict the lookup
1620 */
1621function activateMaskClass($scope)
1622{
1623	/*
1624	 * Masks and validation form input fields
1625	 */
1626	$scope = $scope || document;
1627
1628	// Validates a Social Security field
1629	$('input.validateSSN', $scope).not('[readonly]').mask("?999-99-9999");
1630
1631	// Validate date field
1632	$('input.validateDate', $scope).mask("99/99/99?99");
1633
1634	// Validate date field
1635	$('input.validateDate2', $scope).mask("99/9999");
1636
1637	// Validate a Zip 5 field
1638	$('input.validateZip5', $scope).mask("99999");
1639
1640	// Validate a Zip 9 field
1641	$('input.validateZip9', $scope).mask("99999?-9999");
1642}
1643
1644/**
1645 * Validates file name & checks if it contains any of the illegal characters
1646 * @param  {string} fileName
1647 * @return boolean true/false
1648 */
1649function validateFileName(fileName) {
1650	if (fileName.indexOf('/') > -1 || fileName.indexOf('\\') > -1 || fileName.indexOf('|') > -1 ||
1651		fileName.indexOf('*') > -1 || fileName.indexOf('<') > -1 || fileName.indexOf('>') > -1 ||
1652		fileName.indexOf(':') > -1 || fileName.indexOf('?') > -1 || fileName.indexOf('"') > -1) {
1653		alert('File Name cannot contain any of the following characters: " / \\ * : ? > < |');
1654		return false;
1655	}
1656	return true;
1657}
1658
1659/**
1660 * Similar to htmlspecialchars of PHP, escapes some HTML tags
1661 *
1662 * @param  {string} text
1663 *
1664 * @return string
1665 */
1666function htmlSpecialCharsJS(text) {
1667	if ( ! text || ! text.length) {
1668		return text;
1669	}
1670
1671	var map = {
1672		'&': '&amp;',
1673		'<': '&lt;',
1674		'>': '&gt;',
1675		'"': '&quot;',
1676		"'": '&#039;'
1677	};
1678
1679	return text.replace(/[&<>"']/g, function(m) { return map[m]; });
1680}
1681
1682/**
1683 * Similar to htmlencode of PHP. Ehcodes all special html chars.
1684 *
1685 * @param {string} text Text for encoding.
1686 *
1687 * @return string
1688 */
1689function htmlencode(text) {
1690    return text.replace(/[<>\&\u00A0-\u9999]/gm, function(char) {
1691        return '&#' + char.charCodeAt(0) + ';';
1692    });
1693}
1694
1695/**
1696 * Add zeroes to integer numbers
1697 *
1698 * @param {string} num
1699 */
1700function addZeroes(num) {
1701    var thisVal = num.split(".");
1702    if (thisVal.length == 1) {
1703        return num + ".00";
1704    } else if (thisVal.length > 1 && thisVal[1].length == 1) {
1705        return num + "0";
1706    }
1707    return num;
1708}
1709
1710(function($) {
1711    /**
1712     * Small custom tooltip plugin
1713     * @param {object} options
1714     */
1715    $.fn.myTooltip = function(options) {
1716        var settings = $.extend({
1717            containerClass: 'my-tooltip',
1718            tooltipHtml: '<img src="/images/icons/question_mark_blue.png" />',
1719            operatingClass: 'tooltip-pointer'
1720        }, options);
1721
1722        var tooltipDiv = $("<div></div>").addClass(settings.containerClass);
1723		$('body').append(tooltipDiv);
1724
1725		this.on('mouseover mousemove', function(e) {
1726			var x = e.clientX + 10;
1727			var y = e.clientY + 20;
1728			var toolTipWithLines = $(this).attr('data-tooltiptext') || $(this).attr('tooltiptext');
1729			if (!toolTipWithLines) {
1730				return;
1731			}
1732			tooltipDiv.html("<div style='color:#446;'>"+toolTipWithLines+"</div>");
1733			tooltipDiv.css({left:x, top:y});
1734			tooltipDiv.show();
1735		}).on('mouseout', function() {
1736			tooltipDiv.hide();
1737		});
1738
1739        return this.each(function() {
1740            $(this).html(settings.tooltipHtml).addClass(settings.operatingClass);
1741        });
1742    };
1743
1744    $.fn.submitOnEnter = function(params) {
1745        params    = params === undefined ? {} : params;
1746        var self  = this;
1747        var $form = self.is('form') ? self : self.closest('form');
1748
1749        self.on('keyup', function(e){
1750            switch(e.keyCode) {
1751                case 13:
1752                    if (params.btn !== undefined) {
1753                        params.btn.click();
1754                    } else {
1755                        $form.submit();
1756                    }
1757
1758                    break;
1759            }
1760        });
1761
1762        return this;
1763    }
1764})(jQuery);
1765/*
1766the end
1767*/
1768
1769
1770/**
1771 * Back to top functionality
1772 */
1773jQuery(document).ready(function($){
1774    // browser window scroll (in pixels) after which the "back to top" link is shown
1775    var offset = 300,
1776    //browser window scroll (in pixels) after which the "back to top" link opacity is reduced
1777    offset_opacity = 1200,
1778    //duration of the top scrolling animation (in ms)
1779    scroll_top_duration = 700,
1780    //grab the "back to top" link
1781    $back_to_top = $('.cd-top');
1782
1783    var move_button = function(){
1784        var win = $(window);
1785        if (win.width() < 1330) {
1786            $back_to_top.css('right', '15px');
1787            $back_to_top.css('margin-right', '75px');
1788        }else{
1789            $back_to_top.css('right', '50%');
1790            $back_to_top.css('margin-right', '-565px');
1791        }
1792    };
1793
1794    move_button();
1795    $(window).on('resize', function(){ move_button() });
1796
1797    //hide or show the "back to top" link
1798    $(window).scroll(function(){
1799        ( $(this).scrollTop() > offset ) ? $back_to_top.addClass('cd-is-visible') : $back_to_top.removeClass('cd-is-visible cd-fade-out');
1800        if( $(this).scrollTop() > offset_opacity ) {
1801            $back_to_top.addClass('cd-fade-out');
1802        }
1803    });
1804
1805    //smooth scroll to top
1806    $back_to_top.on('click', function(event){
1807        event.preventDefault();
1808        // Temporarily disable Bootstrap's native smooth scroll to ensure consistent jQuery animation
1809        const htmlElement = document.documentElement;
1810        const originalScrollBehavior = htmlElement.style.scrollBehavior;
1811        htmlElement.style.scrollBehavior = 'auto';
1812
1813        $('body,html').animate({
1814            scrollTop: 0
1815            }, scroll_top_duration, function() {
1816                // Restore original scroll behavior after animation completes
1817                htmlElement.style.scrollBehavior = originalScrollBehavior;
1818            }
1819        );
1820    });
1821
1822});
1823
1824(function ($) {
1825    var datesCache, dateRange;
1826
1827    /**
1828     * Return object of date range specified by 'val' parameter
1829     * Example of output:
1830     * {from: 'mm/dd/yy', to: 'mm/dd/yy'}
1831     *
1832     * @param {String} val    Name of date range which should be returned
1833     * @param {Object} custom Options for 'custom' date range
1834     *
1835     * @return {Object|undefined}
1836     */
1837    dateRange = function (val, custom) {
1838        if (datesCache !== undefined) {
1839            return datesCache[val];
1840        }
1841
1842        var now = new Date(),
1843            d = now.getDate(),
1844            m = now.getMonth() + 1,
1845            y = now.getFullYear(),
1846            to = '';
1847
1848        var lastDayNumber = new Date(y, m, 0).getDate();
1849        var lastDayDate = m + '/' + lastDayNumber + '/' + y;
1850
1851        d = (d < 10) ? '0' + d : d;
1852        m = (m < 10) ? '0' + m : m;
1853
1854        to = m + '/' + d + '/' + y;
1855
1856        datesCache = {};
1857
1858        datesCache[$.viewingDateRange.MONTH_TO_DATE] = {
1859            from: m + '/01/' + y,
1860            to: to
1861        };
1862
1863        datesCache[$.viewingDateRange.TODAY] = {
1864            from: to,
1865            to: to
1866        };
1867
1868        datesCache[$.viewingDateRange.LAST_7_DAYS] = {
1869            from: (function (d, m, y) {
1870                var date = new Date();
1871                date.setDate(date.getDate() - 7);
1872
1873                return (date.getMonth() + 1) + '/' + date.getDate() + '/' + date.getFullYear();
1874            })(d, m, y),
1875            to: to
1876        };
1877
1878        datesCache[$.viewingDateRange.LAST_30_DAYS] = {
1879            from: (function (d, m, y) {
1880                if ((m - 1) < 1) {
1881                    m = 12;
1882                    y -= 1;
1883                } else {
1884                    m -= 1;
1885                }
1886
1887                m = (m < 10) ? '0' + m : m;
1888
1889                return m + '/' + d + '/' + y;
1890            })(d, m, y),
1891            to: to
1892        };
1893
1894        datesCache[$.viewingDateRange.LAST_3_MONTHS] = {
1895            from: (function (d, m, y) {
1896                if ((m - 3) < 1) {
1897                    m = m - 3 + 12;
1898                    y -= 1;
1899                } else {
1900                    m -= 3;
1901                }
1902
1903                m = (m < 10) ? '0' + m : m;
1904
1905                return m + '/' + d + '/' + y;
1906            })(d, m, y),
1907            to: to
1908        };
1909
1910        datesCache[$.viewingDateRange.LAST_6_MONTHS] = {
1911            from: (function (d, m, y) {
1912                if ((m - 6) < 1) {
1913                    m = m - 6 + 12;
1914                    y -= 1;
1915                } else {
1916                    m -= 6;
1917                }
1918
1919                m = (m < 10) ? '0' + m : m;
1920
1921                return m + '/' + d + '/' + y;
1922            })(d, m, y),
1923            to: to
1924        };
1925
1926        datesCache[$.viewingDateRange.YEAR_TO_DATE] = {
1927            from: '01/01/' + y,
1928            to: to
1929        };
1930
1931        datesCache[$.viewingDateRange.LAST_12_MONTHS] = {
1932            from: m + '/' + d + '/' + (y - 1),
1933            to: to
1934        };
1935
1936        datesCache[$.viewingDateRange.CURRENT_MONTH] = {
1937            from: m + '/01/' + y,
1938            to: lastDayDate
1939        };
1940
1941        if (typeof custom.customDate === 'object') {
1942            datesCache[custom.customKey] = custom.customDate;
1943        } else if (datesCache[custom.customDate] !== undefined) {
1944            datesCache[custom.customKey] = datesCache[custom.customDate];
1945        }
1946
1947        return datesCache[val];
1948    };
1949
1950    /**
1951     * Custom jQuery plugin to handle dateRange dropdown and its associated 'From' and 'To' date fields
1952     *
1953     * @param {Object} options Plugin's configuration options
1954     * The list of options:
1955     *   - filterFrom: the jQuery selector or jQuery element of the 'From' date field.
1956     *   - filterTo: the jQuery selector or jQuery element of the 'From' date field.
1957     *   - customKey:
1958     *   - customDate: what date range will be used for custom item of dateRange dropdown when it is selected.
1959     *   - isDefaultDate: whether the date fields ('From', 'To') will be filled after page loading or not.
1960     *   - datepicker: object of the datepicker's options which will be applied for the both date fields ('From', 'To')
1961     *   - datepickerFrom: object of the datepicker's options which will be applied only for the 'From' date field.
1962     *     It will rewrite options from 'datepicker' option
1963     *   - datepickerTo: object of the datepicker's options which will be applied only for the 'To' date field.
1964     *     It will rewrite options from 'datepicker' option
1965     */
1966    $.fn.viewingDateRange = function (options) {
1967        var settings,
1968            jqDateFieldsWrap,
1969            custom;
1970
1971        options = options || {};
1972        settings = $.extend(
1973            {
1974                filterFrom: '#filterFrom',
1975                filterTo: '#filterTo',
1976                customKey: 'custom',
1977                customDate: '',
1978                isDefaultDate: true,
1979                datepicker: {
1980                    changeMonth: true,
1981                    changeYear: true,
1982                    dateFormat: 'mm/dd/yy',
1983                    buttonImage: '/images/icons/date.png',
1984                    buttonImageOnly: true,
1985                    showOn: 'both'
1986                },
1987                datepickerFrom: {
1988                    buttonText: 'From'
1989                },
1990                datepickerTo: {
1991                    buttonText: 'To'
1992                }
1993            },
1994            options
1995        );
1996
1997        custom = {
1998            customKey: settings.customKey,
1999            customDate: settings.customDate
2000        };
2001
2002        if (typeof settings.filterFrom === 'string') {
2003            settings.filterFrom = $(settings.filterFrom);
2004        }
2005
2006        if (typeof settings.filterTo === 'string') {
2007            settings.filterTo = $(settings.filterTo);
2008        }
2009
2010        jqDateFieldsWrap = settings.filterFrom.parent();
2011
2012        this.on('change', function () {
2013            var instFrom, instTo,
2014                dates = dateRange(this.value, custom);
2015
2016            if (dates !== undefined) {
2017                settings.filterFrom.val(dates.from);
2018                settings.filterTo.val(dates.to);
2019
2020                instFrom = $.datepicker._getInst(settings.filterFrom[0]);
2021                instTo   = $.datepicker._getInst(settings.filterTo[0]);
2022
2023                $.datepicker._get(instFrom, 'onSelect').apply(
2024                    instFrom.input[0],
2025                    [settings.filterFrom.datepicker('getDate'), instFrom]
2026                );
2027
2028                $.datepicker._get(instTo, 'onSelect').apply(
2029                    instTo.input[0],
2030                    [settings.filterTo.datepicker('getDate'), instTo]
2031                );
2032            }
2033
2034            if (this.value === settings.customKey) {
2035                jqDateFieldsWrap.show();
2036            } else {
2037                jqDateFieldsWrap.hide();
2038            }
2039        });
2040
2041        settings.filterFrom.datepicker(
2042            $.extend({}, settings.datepicker, settings.datepickerFrom, {
2043                onSelect: function (selectedDate) {
2044                    settings.filterTo.datepicker('option', 'minDate', selectedDate);
2045                },
2046                onChangeMonthYear: function(year, month, inst) {
2047                    var selectedDate = new Date(inst.selectedYear, inst.selectedMonth, inst.selectedDay);
2048
2049                    $(this).datepicker('setDate', selectedDate);
2050                    settings.filterTo.datepicker('option', 'minDate', $(this).datepicker('getDate'));
2051                }
2052            })
2053        );
2054
2055        settings.filterTo.datepicker(
2056            $.extend({}, settings.datepicker, settings.datepickerTo, {
2057                onSelect: function(selectedDate) {
2058                    settings.filterFrom.datepicker('option', 'maxDate', selectedDate);
2059                },
2060                onChangeMonthYear: function(year, month, inst) {
2061                    var selectedDate = new Date(inst.selectedYear, inst.selectedMonth, inst.selectedDay);
2062
2063                    $(this).datepicker('setDate', selectedDate);
2064                    settings.filterFrom.datepicker('option', 'maxDate', $(this).datepicker('getDate'));
2065                }
2066            })
2067        );
2068
2069        if (this.val() !== settings.customKey) {
2070            jqDateFieldsWrap.hide();
2071        }
2072
2073        if (settings.isDefaultDate) {
2074            this.change();
2075        }
2076    };
2077
2078    $.fn.viewingDateTimeRange = function (options) {
2079        var settings,
2080            jqDateFieldsWrap,
2081            custom;
2082
2083        /**
2084         * override "Now" button to also grab the time
2085         */
2086        $.datepicker._gotoToday = function (id) {
2087            var inst = this._getInst($(id)[0]);
2088            this._base_gotoToday(id);
2089            var tp_inst = this._get(inst, 'timepicker');
2090            $.timepicker._util._selectLocalTimezone(tp_inst);
2091            var now = new Date();
2092            var utcNow = new Date(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours(), now.getMinutes(), now.getSeconds());
2093            this._setTime(inst, utcNow);
2094            this._setDate(inst, now);
2095        };
2096
2097        options = options || {};
2098        settings = $.extend(
2099            true,
2100            {
2101                filterFrom: '#filterFrom',
2102                filterTo: '#filterTo',
2103                customKey: 'custom',
2104                customDate: '',
2105                isDefaultDate: true,
2106                hideCalendars: true,
2107                setFromDates: false,
2108                datepicker: {
2109                    changeMonth: true,
2110                    changeYear: true,
2111                    dateFormat: 'mm/dd/yy',
2112                    timeFormat: 'HH:mm',
2113                    buttonImage: '/images/icons/date.png',
2114                    buttonImageOnly: true,
2115                    showOn: 'both'
2116                },
2117                datepickerFrom: {
2118                    buttonText: 'From'
2119                },
2120                datepickerTo: {
2121                    buttonText: 'To'
2122                }
2123            },
2124            options
2125        );
2126
2127        custom = {
2128            customKey: settings.customKey,
2129            customDate: settings.customDate
2130        };
2131
2132        if (typeof settings.filterFrom === 'string') {
2133            settings.filterFrom = $(settings.filterFrom);
2134        }
2135
2136        if (typeof settings.filterTo === 'string') {
2137            settings.filterTo = $(settings.filterTo);
2138        }
2139
2140        jqDateFieldsWrap = settings.filterFrom.parent();
2141
2142        this.on('change', function (event, skipCustomDate) {
2143            var instFrom, instTo,
2144                dates = dateRange(this.value, custom);
2145
2146            if (dates !== undefined && (this.value !== settings.customKey || skipCustomDate !== true)) {
2147                settings.filterFrom.datetimepicker('setDate', new Date(dates.from + ' 00:00'));
2148                settings.filterTo.datetimepicker('setDate', new Date(dates.to + ' 23:59'));
2149
2150                instFrom = $.datepicker._getInst(settings.filterFrom[0]);
2151                instTo = $.datepicker._getInst(settings.filterTo[0]);
2152
2153                $.datepicker._get(instFrom, 'onSelect').apply(
2154                    instFrom.input[0],
2155                    [new Date(settings.filterFrom.val())]
2156                );
2157
2158                $.datepicker._get(instTo, 'onSelect').apply(
2159                    instTo.input[0],
2160                    [new Date(settings.filterTo.val())]
2161                );
2162            }
2163
2164            if (this.value === settings.customKey || !settings.hideCalendars) {
2165                jqDateFieldsWrap.show();
2166            } else {
2167                jqDateFieldsWrap.hide();
2168            }
2169        });
2170
2171        $.timepicker.datetimeRange(
2172            settings.filterFrom,
2173            settings.filterTo,
2174            {
2175                start: $.extend({}, settings.datepicker, settings.datepickerFrom, {
2176                    onChangeMonthYear: function (year, month, inst, timeInst) {
2177                        var time = $.datepicker.parseTime(settings.datepicker.timeFormat, timeInst.formattedTime);
2178                        var selectedDateTime = new Date(inst.selectedYear, inst.selectedMonth, inst.selectedDay, time.hour, time.minute);
2179                        $(this).datetimepicker('setDate', selectedDateTime);
2180                        settings.filterTo.datetimepicker('option', 'minDate', $(this).datetimepicker('getDate'));
2181                    }
2182                }),
2183                end: $.extend({}, settings.datepicker, settings.datepickerTo, {
2184                    onChangeMonthYear: function (year, month, inst, timeInst) {
2185                        var time = $.datepicker.parseTime(settings.datepicker.timeFormat, timeInst.formattedTime);
2186                        var selectedDateTime = new Date(inst.selectedYear, inst.selectedMonth, inst.selectedDay, time.hour, time.minute);
2187                        $(this).datetimepicker('setDate', selectedDateTime);
2188                        settings.filterFrom.datetimepicker('option', 'maxDate', $(this).datetimepicker('getDate'));
2189                    }
2190                })
2191            }
2192        );
2193
2194        if (this.val() !== settings.customKey && settings.hideCalendars) {
2195            jqDateFieldsWrap.hide();
2196        }
2197
2198        if (settings.setFromDates) {
2199            // picks proper value using `from` and `to` dates
2200            dateRange(-1, custom);
2201            for (var key in datesCache) {
2202                var from = settings.filterFrom.val().split(' ').shift();
2203                var to = settings.filterTo.val().split(' ').shift();
2204                if (from == datesCache[key].from && to == datesCache[key].to) {
2205                    this.val(key);
2206                    break;
2207                }
2208            }
2209        }
2210
2211        if (settings.isDefaultDate) {
2212            this.change();
2213        }
2214
2215        return this;
2216    };
2217
2218    // Supported items for the dateRange dropdown
2219    $.viewingDateRange = {
2220        MONTH_TO_DATE: 'mtd',
2221        TODAY: 'td',
2222        LAST_7_DAYS: '7d',
2223        LAST_30_DAYS: '30d',
2224        LAST_3_MONTHS: '3m',
2225        LAST_6_MONTHS: '6m',
2226        YEAR_TO_DATE: 'ytd',
2227        LAST_12_MONTHS: '12m',
2228        CURRENT_MONTH: 'curmon',
2229    };
2230})(jQuery);
2231
2232/**
2233 * Creates a function to send/handle a JSON requests to the server
2234 *
2235 * @param baseURL
2236 * @param {object} [options] - Options for $.ajax
2237 * @param {Function} [successHandler] - Default success handler
2238 * @param {Function} [errorHandler] - Default error handler
2239 * @returns {Function|jsonRequest~request} Function takes two parameters (one, two):
2240 *                          one: a string to append to the {baseUrl}, or an array.
2241 *                          two: a object to send in the request. If param 'one' is an object param 'two' is ignored
2242 */
2243function jsonRequest(baseURL, options, successHandler, errorHandler){
2244
2245    var ajaxOptions = {
2246        // Add the base URL and make sure is has a lead forward slash
2247        url: baseURL + (baseURL.slice(-1) == '/' ? '' : '/'),
2248        contentType : 'application/json',
2249        type : 'POST',
2250        dataType: 'json'
2251    };
2252
2253    ajaxOptions = $.extend(ajaxOptions, options);
2254
2255    if ( typeof  successHandler !== 'function')
2256    {
2257        successHandler = function(){return true;}
2258    }
2259
2260    if ( typeof  errorHandler !== 'function')
2261    {
2262        errorHandler = function(){return true;}
2263    }
2264
2265    // Default filter to handle the request response
2266    var successFilter = function(response){
2267        // Successful request
2268        this.resolve(response);
2269    };
2270
2271    /**
2272     * Executes ajax request
2273     *
2274     * @memberOf jsonRequest
2275     * @inner
2276     *
2277     * @param {object|string}
2277 urlParams - Parameters to append to the base URL
2278     * @param {object} data - Data to send in the request. Consider using {@link toFlatObject} to generate the data from a jQuery object
2279     * @returns {Deferred}
2280     */
2281    function request(urlParams, data){
2282
2283        var deferred = $.Deferred(),
2284            requestOptions = $.extend({},ajaxOptions);
2285
2286        if ( $.isArray(urlParams) && urlParams.length )
2287        {
2288            /*
2289             If provided, add the action url parameters to the base url
2290             */
2291            requestOptions.url += urlParams.join('/');
2292        }
2293        else if( (/string|number/).test(typeof urlParams) )
2294        {
2295            /*
2296             Append the string to the base URL
2297             */
2298            requestOptions.url += urlParams;
2299        } else if ( $.isPlainObject(urlParams) && data === undefined) {
2300            /*
2301             If the first parameter is an object assume it is the data/payload
2302             */
2303            data = urlParams;
2304        }
2305
2306        data = data || requestOptions.data || undefined;
2307
2308        if ( data )
2309        {
2310            /*
2311             If the method is not GET stringify the data
2312             */
2313            requestOptions.data = requestOptions.type != 'GET' ? JSON.stringify(data) : data;
2314        }
2315        else
2316        {
2317            if ( requestOptions.type === 'POST'  ) {
2318                /*
2319                 If no data is passed then change the request type to GET
2320                */
2321                requestOptions.type = 'GET';
2322            }
2323        }
2324
2325        // Add default callbacks
2326        deferred.done(successHandler).fail(errorHandler);
2327
2328        var jqHXR = $.ajax(requestOptions)
2329            // First success handler
2330            .done(successFilter.bind(deferred))
2331            // First error handler
2332            .fail(function(jqHXR, textStatus){
2333                if ( ajaxOptions.laravel && jqHXR.responseJSON !== undefined) {
2334                    deferred.reject(jqHXR.responseJSON, jqHXR.status, jqHXR, textStatus);
2335                } else {
2336                    // Request failed, pass generic message plus the ajax request object and status
2337                    deferred.reject('An unknown error has occurred.', jqHXR, textStatus);
2338                }
2339            });
2340        var promise = deferred.promise();
2341        promise.jqHXR = jqHXR;
2342
2343        return promise;
2344    }
2345
2346    /**
2347     * Add custom response filter
2348     * NOTE: This function MUST 'resolve' or 'reject' the deferred object which is bound to the function.
2349     *       Ex: this.resolve()
2350     *
2351     * By default the 'promise' returned by the request() function is resolved with the response from a
2352     * successful request. This filter allows this behavior to be overwritten so the 'promise' can be
2353     * conditionally resolved or rejected
2354     *
2355     * @param {function} handler
2356     *
2357     * @returns {request}
2358     */
2359    request.setResponseFilter = function(handler){
2360        if ( typeof handler === 'function')
2361        {
2362            successFilter = handler;
2363        }
2364
2365        return this;
2366    };
2367
2368    /**
2369     * Set the default success handler
2370     *
2371     * @param {function} handler
2372     *
2373     * @returns {request}
2374     */
2375    request.setSuccessHandler = function(handler){
2376        if ( typeof handler === 'function')
2377        {
2378            successHandler = handler;
2379        }
2380
2381        return this;
2382    };
2383
2384    /**
2385     * Set the default error handler
2386     *
2387     * @param {function} handler
2388     * @returns {request}
2389     */
2390    request.setErrorHandler = function(handler){
2391        if ( typeof handler === 'function')
2392        {
2393            errorHandler = handler;
2394        }
2395
2396        return this;
2397    };
2398
2399    /**
2400     * Update a single option for $.ajax
2401     *
2402     * @param {String} name
2403     * @param {String} value
2404     *
2405     * @returns {request}
2406     */
2407    request.setOption = function(name, value){
2408        ajaxOptions[name] = value;
2409
2410        return this;
2411    };
2412
2413    /**
2414     * Update multiple options for $.ajax
2415     * @param {Object} updates
2416     *
2417     * @returns {request}
2418     */
2419    request.setOptions = function(updates){
2420        if ( $.isPlainObject(updates) )
2421        {
2422            ajaxOptions = $.extend(ajaxOptions, updates);
2423        }
2424
2425        return this;
2426    };
2427
2428    return request;
2429}
2430
2431(function($){
2432    /**
2433     * Update an object property by nested keys
2434     *
2435     * @param {Object} dataObj
2436     * @param {Array} steps
2437     * @param {*} value
2438     * @param {Boolean}
2438 push
2439     */
2440    function updatePath(dataObj, steps, value, push){
2441        var lastStep = steps.pop(),
2442            lastRef = dataObj;
2443
2444        push = lastStep === '';
2445
2446        if ( steps.length ) {
2447            steps.forEach(function(step){
2448                if ( ! lastRef[step] ) {
2449                    lastRef = lastRef[step] = push ? [] : {}
2450                } else {
2451                    lastRef = lastRef[step];
2452                }
2453            });
2454        } else {
2455            lastRef = lastRef[lastStep] = push ? [] : {}
2456        }
2457
2458        if ( push ) {
2459            if ( typeof lastRef.push !== 'function' ) {
2460                lastRef = []
2461            }
2462
2463            lastRef.push(value)
2464        } else {
2465            lastRef[lastStep] = value;
2466        }
2467    }
2468
2469    /**
2470     * Converts all control element in the selection into a flat key/pair object
2471     * @returns {{}}
2472     */
2473    $.fn.toFlatObject = function(method, includeUnchecked){
2474        var result = {};
2475        method = method === undefined ? 'find' : method;
2476        $(this)[method](':input, :checkbox:checked').serializeArray().forEach(function(entry){
2477            var key = entry.name;
2478            var val = entry.value;
2479            var from = key.indexOf("[");
2480            if ( from === -1 ) {
2481                result[key] = val;
2482            } else {
2483                var keys = key.split('[').map(function(keyPart){
2484                    if ( keyPart ) {
2485                        var to = keyPart.indexOf(']');
2486                        to = to > -1 ? to : keyPart.length;
2487                        return keyPart.substring(0, to);
2488                    }
2489                });
2490
2491                updatePath(result, keys, val, key.indexOf('[]') > -1);
2492            }
2493        });
2494
2495        if (includeUnchecked === true) {
2496            // Include checkboxes field that are check as empty
2497            $(this)[method](':checkbox').each(function () {
2498                if (this.checked === false) {
2499                    if (result[this.name] === undefined) {
2500                        result[this.name] = ''
2501                    }
2502                }
2503            })
2504        }
2505
2506        return result;
2507    }
2508})(jQuery);
2509
2510/**
2511 * Schedule an async task to be performed periodically
2512 *
2513 * @param {int} interval The time to repeat task in minutes
2514 * @param {Function} taskFactory A factory to create a new task. Should return a promise object.
2515 *
2516 * @constructor
2517 */
2518function PeriodicTask(interval, taskFactory){
2519    var taskPool = new SingleTimeout(interval, createTask);
2520
2521    /**
2522     * Creates a new task
2523     */
2524    function createTask(){
2525        var task = taskFactory();
2526
2527        // Handle jQuery promises
2528        if ( typeof task.always === 'function' ) {
2529            task.always(scheduleNextTask);
2530        } else {
2531            // Handle standard promises
2532            if ( typeof task.finally === 'function' ) {
2533                task.finally(scheduleNextTask);
2534            }
2535        }
2536    }
2537
2538    /**
2539     * Schedules the next task
2540     */
2541    function scheduleNextTask(){
2542        taskPool.queue();
2543    }
2544
2545    /**
2546     * Stop the queue. Any running task will not be stopped.
2547     */
2548    this.stop = function(){
2549        taskPool.stop();
2550    }
2551
2552    /**
2553     * Start or restart the schedule
2554     */
2555    this.start = function(){
2556        taskPool.queue();
2557    }
2558
2559    if ( typeof taskFactory === 'function' ) {
2560        // Start the schedule
2561        this.start();
2562    } else {
2563        console.warn('The PeriodicTask task factory must be a function.');
2564    }
2565}
2566
2567
2568/**
2569 * Utility only one instance of a function in a delayed execution using JS timeout functions
2570 *
2571 * @param {int} delay Times in seconds to delay handler execution
2572 * @param {function} [handler] The handler
2573 * @constructor
2574 */
2575function SingleTimeout(delay, handler){
2576    var initDelay, initHandler, timer;
2577
2578    function isCallable(func){
2579        return typeof func == 'function'
2580    }
2581
2582    /**
2583     * Queues handler to be executed after the elapse time
2584     *
2585     * @param {function} [altHandler] Alternative handler function to delay for XX seconds
2586     * @returns {SingleTimeout}
2587     */
2588    this.queue = function(altHandler){
2589        var actualHandler;
2590        if ( isCallable(altHandler)) {
2591            actualHandler = altHandler
2592        } else {
2593            if ( isCallable(initHandler) ) {
2594                actualHandler = initHandler;
2595            }
2596        }
2597
2598        if ( actualHandler ) {
2599            if ( initDelay ) {
2600                clearTimeout(timer);
2601                timer = setTimeout(actualHandler, initDelay);
2602
2603                //Update the default handler
2604                initHandler = actualHandler;
2605            } else {
2606                throw 'No delay setup for the queue timeout';
2607            }
2608        }
2609
2610        return this;
2611    };
2612
2613    /**
2614     * Executes the handler now
2615     */
2616    this.execute = function() {
2617        clearTimeout(timer)
2618        initHandler()
2619    }
2620
2621    /**
2622     * Cancel any item in queue
2623     */
2624    this.cancel = function(){
2625        clearTimeout(timer);
2626    };
2627
2628    /**
2629     * Stops any queued handler
2630     */
2631    this.stop = function(){
2632        clearTimeout(timer);
2633    };
2634
2635    /**
2636     * Set the time in seconds to delay the handler queue
2637     * @param {int} seconds
2638     */
2639    this.delayFor = function(seconds){
2640        initDelay = seconds * 1000;
2641    };
2642
2643    /*
2644     * Initialize the instance
2645     */
2646    if ( delay ) {
2647        this.delayFor(delay);
2648    }
2649
2650    if ( isCallable(handler) ) {
2651        // Set the default handler but do not queue it during initialization
2652        initHandler = handler;
2653    }
2654}
2655
2656/**
2657 * Process a list of items or tasks asynchronously
2658 *
2659 * @param {Array} queue Tasks
2660 * @param {Function} process Handler to process task or items
2661 *
2662 * @return {$.Promise}
2663 */
2664function processQueue(queue, process){
2665    var deferred = $.Deferred();
2666
2667    if ( queue instanceof jQuery) {
2668        queue = queue.toArray();
2669    }
2670
2671    function next(){
2672        requestAnimationFrame(function(){
2673            var result = process(queue[keys.shift()]);
2674
2675            if ( keys.length ) {
2676                if (typeof result.then === 'function') {
2677                    // If a promise like object is returned, wait until it is resolved.
2678                    result.then(next);
2679                } else {
2680                    next();
2681                }
2682            } else {
2683                deferred.resolve();
2684            }
2685        });
2686    }
2687
2688    if ( $.isArray(queue) ) {
2689        var keys = Object.keys(queue);
2690
2691        if ( keys.length ) {
2692            requestAnimationFrame(next);
2693            return deferred.promise();
2694        }
2695    }
2696
2697    return $.when();
2698}
2699
2700/**
2701 * Creates a jQuery dialog factory
2702 *
2703 * @param [defaultOptions] - Any default option for the jQuery Dialog widget plus two additional special flags:
2704 *                              - keep:        whether to auto destroy widget
2705 *                              - canBeClosed: whether to allow the popup to be closed by the user
2706 *                              - content:     The HTML content to use for the dialog
2707 * @returns {Function}
2708 */
2709function createDialog(defaultOptions){
2710    var defaults = {
2711        autoOpen: true,
2712        resizable: false,
2713        modal: false,
2714        closeOnEscape: true,
2715        position: {at: 'top+30%'},
2716
2717        keep: false, // When false the dialog will be destroyed when closed
2718        canBeClosed: true, // When false the dialog can not be closed by the user
2719        canBeApprovedByEnter: false // you can press enter for approving
2720    };
2721
2722    var dialogOptions = $.extend(defaults,defaultOptions);
2723
2724    return function (extraOptions) {
2725        var $container = $('<div></div>'),
2726            options    = $.extend(true, {}, dialogOptions, extraOptions);
2727
2728        if (options.content) {
2729            $container.html(options.content)
2730            // Hack, prevents the first input/button to be focus
2731            .prepend('<span class="ui-helper-hidden-accessible"><input type="text"/></span>');
2732        }
2733
2734        if (options.canBeClosed == false) {
2735            delete options.canBeClosed;
2736
2737            // Prevent the dialog to be close with ESC key if it cannot be confirm by Enter
2738            options.closeOnEscape = options.canBeApprovedByEnter;
2739            options.modal         = true;
2740            options.resizable     = false;
2741            // Removes the close button
2742            $container.on('dialogopen', function (event, ui) {
2743                $(event.currentTarget).siblings('.ui-dialog-titlebar').children('.ui-dialog-titlebar-close').hide();
2744            });
2745        }
2746
2747        if (options.keep !== true) {
2748            // Destroy the widget when closed
2749            $container.on('dialogclose', function (event, ui) {
2750                $container.dialog('destroy');
2751            });
2752        }
2753
2754        if (options.canBeApprovedByEnter === true) {
2755            $container.on('dialogopen', function (event) {
2756                $(event.currentTarget).closest('.ui-dialog').on('keyup', function (e) {
2757                    if (e.keyCode === 13 && $(e.target).is(':not(.chosen-search-input)')) {
2758                        var btnText = defaultOptions && defaultOptions.buttonOk ? defaultOptions.buttonOk : 'OK';
2759                        var btnOk   = $(this).find('.ui-dialog-buttonpane button:contains("' + btnText + '")');
2760                        btnOk.click();
2761                    }
2762                });
2763            });
2764        }
2765
2766        return $container.dialog(options);
2767    };
2768}
2769
2770/**
2771 * Utility to manage status notes like success and error messages
2772 *
2773 * @param {jQuery} $list An empty <ul> element where the notes will added.
2774 * @param {Object=} option List of options to overwrite
2775 * @param {mixed}   option.speed jQuery animation speed
2776 * @param {String}  option.anchor Alternative positioning option for the notes container. Valid opions: inline or right
2777 * @param {Boolean} option.useQueue Whether to queue animations like adding and removing notes
2778 * @constructor
2779 */
2780var StatusNotes = function($list, option){
2781    if ( $list instanceof jQuery && $list.is('ul') ) {
2782        $list.addClass('statusNotes');
2783    } else {
2784        console.warn('The StatusNotes list container must be a <ul> element');
2785    }
2786
2787    $list.data('statusNotesInstance', this);
2788
2789    var parent = this,
2790        lastPromise = $().promise();
2791
2792    option = $.extend({
2793        speed:  250,
2794        anchor: '',
2795        useQueue: true
2796    }, option);
2797
2798    // Handles the custom list position
2799    if ( option.anchor ) {
2800        switch(option.anchor){
2801            case 'right':
2802                $list.addClass('anchor-right');
2803                break;
2804            case 'inline':
2805                $list.addClass('inline');
2806                break;
2807        }
2808    }
2809
2810    /**
2811     * Adds instance methods to other objects
2812     *
2813     * @param obj
2814     * @returns {StatusNotes}
2815     */
2816    function selfChain(obj){
2817        for(var method in parent){
2818            if (parent.hasOwnProperty(method) && typeof parent[method] === 'function' ) {
2819                // Mirror the parent methods
2820                (function(subMethod, subObj){
2821                    subObj[subMethod] = function(){
2822                        var args = Array.prototype.slice.call(arguments);
2823                        return parent[subMethod].apply(parent, args);
2824                    }
2825                })(method, obj);
2826            }
2827        }
2828
2829        return obj;
2830    }
2831
2832    /**
2833     * A note object
2834     * @param {jQuery} $note
2835     * @param {Promise} promise
2836     * @constructor
2837     */
2838    function Note($note, promise){
2839        /**
2840         * Hides the note
2841         */
2842        this.hide = function(){
2843            return dismiss($note).then(function(){
2844                $note.trigger('hidden');
2845                $note.remove();
2846            });
2847        };
2848
2849        /**
2850         * Hides the note after a few seconds
2851         * @param {number} seconds The number of seconds to wait before closing the note
2852         */
2853        this.hideIn = function(seconds){
2854            setTimeout(this.hide.bind(this), (seconds * 1000) + option.speed);
2855        };
2856
2857        /**
2858         * The not element
2859         * @return {jQuery} The note element
2860         */
2861        this.elem = function(){
2862            return $note;
2863        };
2864
2865        /**
2866         * Defer an action for when the note has been displayed
2867         *
2868         * @param {Function} callback Callback
2869         * @return {Note}
2870         */
2871        this.then = function(callback){
2872            if ( typeof callback === 'function' ) {
2873                if ( promise && promise.then ) {
2874                    promise.then(callback);
2875                } else {
2876                    callback();
2877                }
2878            }
2879
2880            return this;
2881        };
2882
2883        /**
2884         * Register callback triggered after the node is dismissed or hidden
2885         *
2886         * @param {Function} callback
2887         * @return {Note}
2888         */
2889        this.afterHide = function(callback){
2890            if ( typeof callback === 'function' ) {
2891                $note.on('hidden', callback);
2892            }
2893
2894            return this;
2895        }
2896
2897        selfChain(this);
2898    }
2899
2900    /**
2901     * Remove a note
2902     * @param $note
2903     */
2904    function dismiss($note){
2905        return $($note).stop(true, true).slideUp(option.speed).promise();
2906    }
2907
2908    /**
2909     * Add a new note to the list
2910     * @param {string} type The note type: error, success
2911     * @param {string|Array} message The note message
2912     * @param {number=} lifespan How long to display the note for in seconds
2913     * @return {Note} Returns the reference of the last note added
2914     */
2915    function add(type, message, lifespan){
2916        var note,
2917            addDelay = 0;
2918        if ( $.isArray(message) ) {
2919            message.forEach(function(msg){
2920                note = add(type, msg, lifespan);
2921                addDelay += option.speed - 100;
2922
2923                if ( lifespan ) {
2924                    lifespan += (addDelay / 1000)
2925                }
2926            });
2927        } else {
2928            var $note = $('<li>').addClass('note ' + type).text(message).hide();
2929
2930            // Defer adding a new note
2931            var promise = queueAction(function(done){
2932                $note.appendTo($list).slideDown(option.speed).promise().then(done);
2933            });
2934
2935            note = new Note($note, promise);
2936
2937            if ( lifespan ) {
2938                note.hideIn(lifespan);
2939            }
2940        }
2941
2942        return note;
2943    }
2944
2945    /**
2946     * Queue an action to be performed after in order after the last promise in queue is resolved
2947     * @param {Function} handler Function to perform asyncronous action. Receives a done handler
2948     */
2949    function queueAction(handler){
2950        if ( option.useQueue ) {
2951            var defer = $.Deferred();
2952            lastPromise.then(function(){
2953                handler(function(){
2954                    var args = Array.prototype.slice.call(arguments);
2955                    if ( args.length ) {
2956                        defer.resolve.apply(defer, args);
2957                    } else {
2958                        defer.resolve();
2959                    }
2960                });
2961            });
2962            return lastPromise = defer.promise();
2963        }
2964
2965        // Execute action immediately
2966        handler(function(){});
2967        return lastPromise;
2968    }
2969
2970    /**
2971     * Clear all the notes from the list
2972     * @param callback
2973     * @return {StatusNotes}
2974     */
2975    this.clear = function(callback){
2976        queueAction(function(done){
2977            var $nList = $list.children();
2978
2979            if ( option.useQueue ) {
2980                processQueue($nList, dismiss).then(callback).then(function(){
2981                    // Finally remove then all elements from the DOM
2982                    $nList.remove();
2983                    // All actions completed
2984                    done();
2985                });
2986            } else {
2987                dismiss($nList).then(function(){
2988                    $nList.remove();
2989                });
2990            }
2991        });
2992
2993        return selfChain(lastPromise);
2994    };
2995
2996    /**
2997     * Add an error note to the list
2998     * @param {string|Array} message
2999     * @param {number} lifespan
3000     * @return {Note}
3001     */
3002    this.error = function(message, lifespan){
3003        return add('error', message, lifespan);
3004    };
3005
3006    /**
3007     * Add an warning note to the list
3008     * @param {string|Array} message
3009     * @param {number} lifespan
3010     * @return {Note}
3011     */
3012    this.warn = function(message, lifespan){
3013        return add('warn', message, lifespan);
3014    };
3015
3016    /**
3017     * Adds a success note to the list
3018     * @param {string|Array} message
3019     * @param {number} lifespan
3020     * @return {Note}
3021     */
3022    this.success = function(message, lifespan){
3023        return add('success', message, lifespan);
3024    };
3025
3026    /**
3027     * Adds an info note to the list
3028     * @param {string|Array} message
3029     * @param {number} lifespan
3030     * @return {Note}
3031     */
3032    this.info = function(message, lifespan){
3033        return add('info', message, lifespan);
3034    };
3035
3036    /**
3037     * Adds a loading message
3038     * @param {string} text
3039     * @return {Note}
3040     */
3041    this.loading = function(text){
3042        return add('loading', text);
3043    };
3044
3045    /**
3046     * Clears the list and adds a loading saving note
3047     * @return {Note}
3048     */
3049    this.saving = function(){
3050        this.clear();
3051        return this.loading('Saving...');
3052    }
3053};
3054
3055/**
3056 * Initialize a notes list or use existing instance
3057 *
3058 * @param {jQuery} $list The list DOM reference
3059 * @param {Object=} options Additional options
3060 * @return {*|StatusNotes}
3061 */
3062StatusNotes.init = function($list, options){
3063    return $list.data('statusNotesInstance') || new StatusNotes($list, options);
3064}
3065
3066/**
3067 * Opens a prompt dialog with two user options: Cancel, OK
3068 *
3069 * @param {String|jQuery} content - The content of the dialog, can be plain text or html
3070 * @param {String} [title=Prompt] - The title of the dialog
3071 * @param {String} [defaultValue=Text] - Default value shown in the input field
3072 * @param {Object.<string>} [extraOptions] - Additional dialog options
3073 * @returns {Deferred} - When 'Cancel' is clicked the promise is resolve with (null).
3074 *                       When 'Ok' is clicked the promise is resolve with (value of input box, formFields), where 'formFields' is an
3075 *                       object of any form elements and their value in the dialog content.
3076 */
3077function promptDialog(content, title, defaultValue, extraOptions) {
3078    defaultValue = JSON.stringify(defaultValue || '').slice(1, -1); // escape string
3079    content += '<div class="useNiceInput"><input class="js-input" type="text" maxlength="50" value="' +
3080        defaultValue + '" style="width: 96%;margin-top: 10px;"></div>';
3081    var deferred = $.Deferred(),
3082        options = {
3083            canBeClosed: false,
3084            title: title || 'Prompt',
3085            content: content,
3086            buttons: [
3087                {
3088                    text: 'Cancel',
3089                    icons: {primary: 'ui-icon-closethick'},
3090                    click: function () {
3091                        deferred.resolve(null);
3092                        $(this).dialog('close');
3093                    }
3094                },
3095                {
3096                    text: 'OK',
3097                    icons: {primary: 'ui-icon-check'},
3098                    click: function () {
3099                        var textBoxValue = $('.js-input', $(this)).val();
3100                        deferred.resolve(textBoxValue, $(promise.dialogContent).toFlatObject());
3101                        $(this).dialog('close');
3102                    }
3103                }
3104            ]
3105        };
3106
3107    var promise = deferred.promise();
3108
3109    promise.dialogContent = createDialog($.extend({}, options, extraOptions))();
3110
3111    return promise;
3112}
3113
3114/**
3115 * Opens a confirmation dialog with two user options: Cancel, Ok
3116 *
3117 * @param {String|jQuery} content - The content of the dialog, can be plain text or html
3118 * @param {String} [title=Confirmation] - The title of the dialog
3119 * @param {Object.<string>} [extraOptions] - Additional dialog options
3120 * @returns {Promise} - When 'Cancel' is clicked the promise is resolve with (false).
3121 *                       When 'Ok' is clicked the promise is resolve with (true, formFields), where 'formFields' is an
3122 *                       object of any form elements and their value in the dialog content.
3123 */
3124function confirmDialog(content, title, extraOptions) {
3125    var deferred = $.Deferred(),
3126        resolved = false
3127
3128    // Flag promise as resolved
3129    deferred.always(() => resolved = true)
3130
3131    var options  = {
3132        canBeClosed: false,
3133        canBeApprovedByEnter: true,
3134        title: title || 'Confirmation',
3135        content: content,
3136        close: function () {
3137            if (! resolved) {
3138                deferred.resolve(false)
3139            }
3140        },
3141        buttons: [
3142            {
3143                text: extraOptions && extraOptions.buttonCancel ? extraOptions.buttonCancel : 'Cancel',
3144                icons: {primary: extraOptions && extraOptions.cancelBtnIcon ? extraOptions.cancelBtnIcon : 'ui-icon-closethick'},
3145                click: function () {
3146                    deferred.resolve(false);
3147                    $(this).dialog('close');
3148                },
3149                class: extraOptions && extraOptions.cancelBtnClass ? extraOptions.cancelBtnClass : ''
3150            },
3151            {
3152                text: extraOptions && extraOptions.buttonOk ? extraOptions.buttonOk : 'OK',
3153                icons: {primary: extraOptions && extraOptions.okBtnIcon ? extraOptions.okBtnIcon : 'ui-icon-check'},
3154                click: function () {
3155                    deferred.resolve(true, $(promise.dialogContent).toFlatObject());
3156                    $(this).dialog('close');
3157                },
3158                class: extraOptions && extraOptions.okBtnClass ? extraOptions.okBtnClass : ''
3159            }
3160        ]
3161    };
3162
3163    var promise = deferred.promise();
3164
3165    promise.dialogContent = createDialog($.extend({}, options, extraOptions))();
3166
3167    return promise;
3168}
3169
3170/**
3171 * Opens a confirmation dialog with two user options: Cancel, Ok
3172 * And add possibility to confirm and close dialog from keyboard
3173 *
3174 * @param {String|jQuery} content - The content of the dialog, can be plain text or html
3175 * @param {String} [title=Confirmation] - The title of the dialog
3176 * @param {Object.<string>} [extraOptions] - Additional dialog options
3177 * @returns {Deferred} - When 'Cancel' is clicked the promise is resolve with (false).
3178 *                       When 'Ok' is clicked the promise is resolve with (true, formFields), where 'formFields' is an
3179 *                       object of any form elements and their value in the dialog content.
3180 */
3181function confirmDialogWithKeyboardButtons(content, title, extraOptions) {
3182    options = {
3183        canBeClosed: true,
3184        canBeApprovedByEnter: true
3185    };
3186
3187    return confirmDialog(content, title, $.extend({}, options, extraOptions))
3188}
3189
3190/**
3191 * Open an alert dialog with only one user option: Ok
3192 *
3193 * @param {String|jQuery} content - The content of the dialog, can be plain text or html
3194 * @param {string} [title=Notice] - The title of dialog
3195 * @param {Object.<string>} [extraOptions] - Additional dialog options
3196 * @returns {Deferred} - When 'Ok' is clicked the promise is resolved with (true)
3197 */
3198function alertDialog(content, title, extraOptions) {
3199    var deferred = $.Deferred(),
3200        options = {
3201            canBeClosed: false,
3202            title: title || 'Notice',
3203            content: content,
3204            buttons: [
3205                {
3206                    text: extraOptions && extraOptions.buttonTitle ? extraOptions.buttonTitle : 'OK',
3207                    icons: {primary: 'ui-icon-check'},
3208                    click: function () {
3209                        deferred.resolve(true);
3210                        $(this).dialog('close');
3211                    }
3212                }
3213            ]
3214        };
3215
3216    var promise = deferred.promise();
3217
3218    promise.dialogContent = createDialog($.extend({}, options, extraOptions))();
3219
3220    return promise;
3221}
3222
3223/**
3224 * Open an alert dialog with only one user option: Ok
3225 * And add possibility to confirm and close dialog from keyboard
3226 *
3227 * @param {String|jQuery} content - The content of the dialog, can be plain text or html
3228 * @param {String} [title=Confirmation] - The title of the dialog
3229 * @param {Object.<string>} [extraOptions] - Additional dialog options
3230 * @returns {Deferred} - When 'Cancel' is clicked the promise is resolve with (false).
3231 *                       When 'Ok' is clicked the promise is resolve with (true, formFields), where 'formFields' is an
3232 *                       object of any form elements and their value in the dialog content.
3233 */
3234function alertDialogWithKeyboardButtons(content, title, extraOptions) {
3235    options = {
3236        canBeClosed: true,
3237        canBeApprovedByEnter: true
3238    };
3239
3240    return alertDialog(content, title, $.extend({}, options, extraOptions))
3241}
3242
3243function loaderDialog(titleText, bodyText, options){
3244    titleText = titleText === undefined ? 'Please wait' : titleText;
3245    bodyText = bodyText === undefined ? 'Loading Information...' : bodyText;
3246
3247    return new function(){
3248        this.content = createDialog(Object.assign({
3249            canBeClosed: false,
3250            modal: true,
3251            title: titleText,
3252            content: '<div class="text-center"><br/><img src="/images/ajax-loader.gif" > <br/><br/> <p class="loading-text">'+bodyText+'</p></div>'
3253        }, options))();
3254
3255        /**
3256         * Closes the dialog
3257         */
3258        this.close = function(){
3259            this.content.dialog('close');
3260        }
3261
3262        /**
3263         * Destroys the dialog
3264         */
3265        this.destroy = function () {
3266            this.content.dialog('destroy').remove();
3267        }
3268
3269        /**
3270         * Updates the dialog title and text
3271         */
3272        this.update = function ({ title = null, text = null }) {
3273            if (title !== null && title !== undefined) {
3274                this.content.dialog('option', { title });
3275            }
3276            if (text !== null && text !== undefined) {
3277                this.content.find('.loading-text').text(text);
3278            }
3279        }
3280
3281        /**
3282         * Perform asynchronous action automatically close loader dialog
3283         *
3284         * @param promiseOrProvider
3285         */
3286        this.waitFor = function(promiseOrProvider){
3287            var promise;
3288            if ( typeof promiseOrProvider === 'function' ) {
3289                promise = promiseOrProvider(this);
3290            } else {
3291                if ( typeof promiseOrProvider.then === 'function' ) {
3292                    promise = promiseOrProvider;
3293                }
3294            }
3295
3296            if ( promise ) {
3297                if (promise.always) {
3298                    promise.always(() => this.close())
3299                } else {
3300                    promise.then(() => this.close())
3301                }
3302            }
3303        }
3304    };
3305}
3306
3307/**
3308 * Helper class for handle local storage flags
3309 *
3310 * @param {string} flag Unique flag key
3311 * @param {bool} session Whether to use the session storage instead of local storage
3312 * @constructor
3313 */
3314function UserFlag (flag, session) {
3315    const KEY   = `${btoa(globalParameters.email).replace(/=+$/, '')}:${flag}`
3316    const store = session ? window.sessionStorage : window.localStorage
3317
3318    this.exists = () => this.get() !== null
3319
3320    this.set = (value) => store.setItem(KEY, value || '1')
3321
3322    this.get = () => store.getItem(KEY)
3323
3324    this.remove = () => store.removeItem(KEY)
3325}
3326
3327/**
3328 * Small utility to scroll to an element
3329 *
3330 * @param options
3331 */
3332$.fn.panTo = function(options) {
3333    var settings = $.extend({
3334        // Defaults
3335        duration: 500,
3336        target: this,
3337        offset: 100
3338    }, options);
3339
3340    var callback = typeof options === 'function' ? options : settings.callback;
3341
3342    $('html, body').animate({
3343        scrollTop: $(settings.target).offset().top - settings.offset
3344    }, settings.duration, callback);
3345
3346    return this;
3347};
3348
3349/**
3350 * Checkboxes handler.
3351 *
3352 * @param checkBoxSelector         Selector for checkboxes
3353 * @param checkAllCheckboxSelector Selected for Check All checkbox that should control other checkboxes
3354 * @param checkBoxChangedEvent     Change event for checkboxes, if nothing provided it will use default change event
3355 * @param allCheckBoxChangedEvent  Change event for All Checkbox, if nothing provided it will use default change event
3356 */
3357function checkboxCheckUncheckHandler(checkBoxSelector, checkAllCheckboxSelector, checkBoxChangedEvent, allCheckBoxChangedEvent) {
3358    if (!$.isFunction(checkBoxChangedEvent)) {
3359        checkBoxChangedEvent = function() {
3360            if ($(checkBoxSelector).length == $(checkBoxSelector + ':checked').length) {
3361                $(checkAllCheckboxSelector).prop('checked', true);
3362            } else {
3363                $(checkAllCheckboxSelector).prop('checked', false);
3364            }
3365        }
3366    }
3367
3368    if (!$.isFunction(allCheckBoxChangedEvent)) {
3369        allCheckBoxChangedEvent = function() {
3370            $(checkBoxSelector).prop('checked', $(this).is(':checked'));
3371        }
3372    }
3373
3374    if ($(checkBoxSelector).length == $(checkBoxSelector + ':checked').length) {
3375        $(checkAllCheckboxSelector).prop('checked', true);
3376    } else {
3377        $(checkAllCheckboxSelector).prop('checked', false);
3378    }
3379
3380    $(checkBoxSelector).on('change', checkBoxChangedEvent);
3381    $(checkAllCheckboxSelector).on('change', allCheckBoxChangedEvent);
3382}
3383
3384/**
3385 * Utility to build pretty date strings
3386 * Example: prettyDate().format() == 09/24/2015
3387 *
3388 * @param {string} [dateString] - string to pass to the new Date() constructor
3389 *
3390 * @returns {}
3391 */
3392function prettyDate(dateString){
3393    var date = dateString == undefined ? new Date() : new Date(dateString),
3394        fullYear = date.getFullYear(),
3395        year = date.getYear(),
3396        month = date.getMonth() + 1,
3397        day = date.getDate();
3398
3399    if(month <= 9)
3400    {
3401        month = '0'+month;
3402    }
3403
3404    if(day <= 9)
3405    {
3406        day = '0'+day;
3407    }
3408
3409    /*
3410        Keys collection to build the formatted date string
3411     */
3412    var formatKeys = {
3413        month:    month,
3414        day:      day,
3415        year:     year,
3416        fullYear: fullYear
3417    };
3418
3419    return new function(){
3420        /**
3421         *
3422         * @param {Array} [order] - The order or date elements, by default is ['month', 'day', 'fullYear']
3423         * @param {String} [separator] - String to use as the separator of the date elements
3424         * @returns {string|*|jQuery}
3425         */
3426        this.format = function(order, separator){
3427            order = order == undefined ? ['month', 'day', 'fullYear'] : order;
3428            separator = separator == undefined ? '/' : separator;
3429
3430            if ( $.isArray(order) )
3431            {
3432                return $(order).map(function(key, value){
3433                    return formatKeys[value];
3434                }).toArray().join(separator);
3435            }
3436            else
3437            {
3438                throw 'Invalid Order provided.';
3439            }
3440        }
3441    };
3442}
3443
3444/**
3445 * Converts placeholders like [#1234] to &#1234; to show emoji
3446 * Used in JS generated strings (Lead Notes, Notifications)
3447 *
3448 * @param {string} text - string contains emoji
3449 *
3450 * @returns {string}
3451 */
3452function convertPlaceholdersToEmoji(text) {
3453    return text.replace(/\[#([^\]]*)\]/g, '&#$1;');
3454}
3455
3456/**
3457 * Returns HTML to show dialer call icon
3458 * @param {object} params - list of parameters
3459 * @returns {string}
3460 */
3461function getDialerCallIcon(params) {
3462    var phoneNumber = params.phoneNumber ? params.phoneNumber : '';
3463    var leadId = params.leadId ? params.leadId : '';
3464    var name = params.name ? params.name : '';
3465    var fieldName = params.fieldName ? params.fieldName : '';
3466    var url = params.url ? params.url : '';
3467    var contactName = params.contactName ? params.contactName : '';
3468    var title = params.title ? params.title : 'Click to Dial';
3469    var extraClass = params.extraClass ? params.extraClass : '';
3470    return "<span class='dialer-call js-dialer-call " + extraClass + "' " +
3471        "data-phone='" + phoneNumber + "' " +
3472        "data-lead_id='" + leadId + "' " +
3473        "data-name='" + name + "' " +
3474        "data-field_name='" + fieldName + "' " +
3475        "data-url='" + url + "' " +
3476        "data-contact_name='" + contactName + "' " +
3477        "title='" + title + "'></span>";
3478}
3479
3480/**
3481 * Returns HTML to show dialer sms icon
3482 * @param {object} params - list of parameters
3483 * @returns {string}
3484 */
3485function getDialerMessageIcon(params) {
3486    var phoneNumber = params.phoneNumber ? params.phoneNumber : '';
3487    var leadId = params.leadId ? params.leadId : '';
3488    var title = params.title ? params.title : 'Click to Dial';
3489    var extraClass = params.extraClass ? params.extraClass : '';
3490    return "<span class='dialer-sms js-dialer-sms" + extraClass + "' " +
3491        "data-phone='" + phoneNumber + "' " +
3492        "data-lead_id='" + leadId + "' " +
3493        "title='" + title + "'></span>";
3494}
3495
3496/**
3497 * This section adds some jQuery methods that were deprecated but we still use.
3498 * Avoid using them
3499 */
3500var jQueryLegacy = new function() {
3501	// Alert and log method
3502	this.log = function(str) {
3503		if (console && console.warn) {
3504			console.warn("Deprecated jQuery: "+str);
3505			if ( console.trace ) {
3506				console.trace();
3507			}
3508		}
3509
3510    };
3511
3512    // $.browser method
3513    this.browser = function() {
3514        //this.log('".browser" is deprecated. (http://api.jquery.com/jquery.browser/)');
3515        ua = navigator.userAgent.toLowerCase();
3516
3517        var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
3518            /(webkit)[ \/]([\w.]+)/.exec( ua ) ||
3519            /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
3520            /(msie) ([\w.]+)/.exec( ua ) ||
3521            /(trident).*rv:([\w.]+)/.exec( ua ) ||
3522            /(edge)\/([\w.]+)/.exec( ua ) ||
3523            ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
3524            [];
3525
3526		var matched = {
3527			browser: match[ 1 ] || "",
3528			version: match[ 2 ] || "0"
3529		};
3530
3531		var browser = {};
3532		if (matched.browser) {
3533			browser[ matched.browser ] = true;
3534			browser.version = matched.version;
3535		}
3536
3537        // Chrome is Webkit, but Webkit is also Safari.
3538        if (browser.chrome) {
3539            browser.webkit = true;
3540        } else if (browser.webkit) {
3541            browser.safari = true;
3542        }
3543
3544        if (browser.trident || browser.edge) {
3545            browser.msie = true;
3546        }
3547
3548		return browser;
3549	};
3550};
3551
3552/**
3553 * There is a bug in IE when page has an url with '#' and has flash/swf embedded in it
3554 * @see{@url http://stackoverflow.com/questions/4562423/ie-title-changes-to-afterhash-if-the-page-has-a-url-with-and-has-flash-s}
3555 */
3556var isIEBrowser = /*@cc_on!@*/false;
3557if (isIEBrowser) {
3558    $(window).on('load', function() {
3559        var title = document.title;
3560        document.title = title;
3561    });
3562
3563    $(window).on('load', function() {
3564        var titleEl = document.getElementsByTagName("title")[0];
3565        var docEl = document.documentElement;
3566
3567		var removeHashFromTitle = function() {
3568			var pos = document.title.indexOf('#');
3569
3570			if (pos !== -1) {
3571				document.title = document.title.slice(0, pos);
3572			}
3573		};
3574
3575		if (docEl && docEl.addEventListener) {
3576			docEl.addEventListener("DOMSubtreeModified", function(evt) {
3577				var t = evt.target;
3578				if (t === titleEl || (t.parentNode && t.parentNode === titleEl)) {
3579					removeHashFromTitle();
3580				}
3581			}, false);
3582		} else {
3583			document.onpropertychange = function() {
3584				if (window.event.propertyName == "title") {
3585					removeHashFromTitle();
3586				}
3587			};
3588		}
3589	});
3590}
3591
3592// Encode/Decode values to Base64
3593var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(e){var t="";var n,r,i,s,o,u,a;var f=0;e=Base64._utf8_encode(e);while(f<e.length){n=e.charCodeAt(f++);r=e.charCodeAt(f++);i=e.charCodeAt(f++);s=n>>2;o=(n&3)<<4|r>>4;u=(r&15)<<2|i>>6;a=i&63;if(isNaN(r)){u=a=64}else if(isNaN(i)){a=64}t=t+this._keyStr.charAt(s)+this._keyStr.charAt(o)+this._keyStr.charAt(u)+this._keyStr.charAt(a)}return t},decode:function(e){var t="";var n,r,i;var s,o,u,a;var f=0;e=e.replace(/[^A-Za-z0-9+/=]/g,"");while(f<e.length){s=this._keyStr.indexOf(e.charAt(f++));o=this._keyStr.indexOf(e.charAt(f++));u=this._keyStr.indexOf(e.charAt(f++));a=this._keyStr.indexOf(e.charAt(f++));n=s<<2|o>>4;r=(o&15)<<4|u>>2;i=(u&3)<<6|a;t=t+String.fromCharCode(n);if(u!=64){t=t+String.fromCharCode(r)}if(a!=64){t=t+String.fromCharCode(i)}}t=Base64._utf8_decode(t);return t},_utf8_encode:function(e){e=e.replace(/rn/g,"n");var t="";for(var n=0;n<e.length;n++){var r=e.charCodeAt(n);if(r<128){t+=String.fromCharCode(r)}else if(r>127&&r<2048){t+=String.fromCharCode(r>>6|192);t+=String.fromCharCode(r&63|128)}else{t+=String.fromCharCode(r>>12|224);t+=String.fromCharCode(r>>6&63|128);t+=String.fromCharCode(r&63|128)}}return t},_utf8_decode:function(e){var t="";var n=0;var r=c1=c2=0;while(n<e.length){r=e.charCodeAt(n);if(r<128){t+=String.fromCharCode(r);n++}else if(r>191&&r<224){c2=e.charCodeAt(n+1);t+=String.fromCharCode((r&31)<<6|c2&63);n+=2}else{c2=e.charCodeAt(n+1);c3=e.charCodeAt(n+2);t+=String.fromCharCode((r&15)<<12|(c2&63)<<6|c3&63);n+=3}}return t}}
3594
3595/**
3596 * Creates datetimepicker with custom onSelect
3597 *
3598 * @param $element
3599 * @param onSelect
3600 */
3601function createDateTimePicker($element, onSelect)
3602{
3603    return $element.datetimepicker({
3604        showOn: "both",
3605        showAnim: 'fadeIn',
3606        buttonImage: "/images/icons/date.png",
3607        buttonImageOnly: false,
3608        ampm: true,
3609        dateFormat: 'mm/dd/yy',
3610        timeFormat: 'h:mm tt',
3611        separator: ' @ ',
3612        controlType: 'select',
3613        oneLine: true,
3614        minDateTime: new Date((new Date()).getTime() + 5*60000),
3615        beforeShow: function(a, b) {
3616            $(a).data('dpDiv', b.dpDiv);
3617        },
3618        onSelect: function($element, $obj) {
3619            onSelect($element, $obj);
3620        }
3621    }).on('focus', function(e) {
3622        $(e.target).data('dpDiv').find('.ui-datepicker-current').remove();
3623    });
3624}
3625
3626/**
3627 * Creates a reschedule email dialog with given open function and success button
3628 *
3629 * @param $element
3630 * @param openFunc
3631 * @param closeFunc
3632 * @param successButton
3633 * @param cancelButton
3634 */
3635function createRescheduleEmailDialog($element, openFunc, closeFunc, successButton, cancelButton) {
3636    var defaultCancelButton = {
3637        text:  "Cancel",
3638        icons: {
3639            primary: 'ui-icon-trash'
3640        },
3641        click: function(){
3642            $(this).dialog('close');
3643        }
3644    };
3645
3646    cancelButton = cancelButton === undefined || !cancelButton ? defaultCancelButton : cancelButton;
3647
3648    return $element.dialog({
3649        autoOpen: false,
3650        modal: true,
3651        resizable: false,
3652        open: openFunc,
3653        beforeClose: closeFunc,
3654        buttons: {
3655            btn1: cancelButton,
3656            btn2: successButton
3657        }
3658    });
3659}
3660
3661function leadField_txt_zipCode($scope) {
3662    var $obj = $scope.find('.zip-code-auto-complete');
3663
3664    $obj.each(function(i) {
3665        zipCodeAutoComplete($('input[name='+$(this).attr('name')+']'));
3666    });
3667}
3668
3669function leadFieldJS_txtSic($scope) {
3670    var $obj = $scope.find('.type-txtsic');
3671
3672    /* Load TxtSic JS if at least one field exists */
3673    if ($obj && $obj.length) {
3674        $obj.autocomplete({
3675            minLength: 3,
3676            selectFirst: true,
3677            source: '/ajax/get_sic',
3678            focus: function(event, ui) {
3679                var toField = $(this).data('customoptions');
3680
3681                if ( toField ) {
3682                    $("[name=" + toField + "]").val(ui.item.code);
3683                }
3684
3685                return false;
3686            },
3687            select: function(event, ui) {
3688                let maxLength = event.target.getAttribute('maxlength');
3689                if (maxLength > 0) {
3690                    $(this).val(ui.item.descr.substr(0, maxLength));
3691                } else {
3692                    $(this).val(ui.item.descr);
3693                }
3694
3695                var toField = $(this).data('customoptions');
3696
3697                if ( toField ) {
3698                    $("[name=" + toField + "]").val(ui.item.code).change();
3699                }
3700
3701                return false;
3702            }
3703        });
3704
3705        $(".type-txtsic").each(function(index, el) {
3706            $(this).data("ui-autocomplete")._renderItem = function( ul, item ) {
3707                return $("<li></li>")
3708                    .data("ui-autocomplete-item", item)
3709                    .append("<a><b>" + item.code + "</b> - " + item.descr + "</a>")
3710                    .appendTo(ul);
3711            };
3712        });
3713    }
3714}
3715
3716function leadFieldJS_txtRouting($scope) {
3717    var $obj = $scope.find('.type-txtrouting');
3718
3719    /* Load TxtRouting JS if at least one field exists */
3720    if ($obj && $obj.length) {
3721        $obj.autocomplete({
3722            selectFirst: true,
3723            minLength:   3,
3724            dataType:    'json',
3725            source:      '/ajax/get_aba',
3726            focus:       function(event, ui){
3727                $(this).val(ui.item.ABA);
3728                return false;
3729            },
3730            select:      function(event, ui){
3731                var toField = $(this).data('customoptions');
3732
3733                if ( toField ) {
3734                    $("[name=" + toField + "]").val(ui.item.CustomerName);
3735                }
3736
3737                return false;
3738            }
3739        });
3740
3741        $obj.each(function(index, el) {
3742            $(this).data("ui-autocomplete")._renderItem = function(ul, item) {
3743                return $("<li></li>")
3744                    .data("ui-autocomplete-item", item)
3745                    .append("<a><b>ABA: " + item.ABA + "</b><br/>Bank: " + item.CustomerName + "<br />
3745" + item.City + ", " + item.State + "</a>")
3746                    .appendTo(ul);
3747            }
3748        });
3749    }
3750}
3751
3752function leadFieldJS_phoneMask($scope) {
3753    var $obj = $scope.find('.type-phonemask');
3754
3755    /* Load PhoneMask JS if at least one field exists */
3756    if ($obj && $obj.length) {
3757
3758        $obj.on("focus", function(e) {
3759            var inputLength = $(this).val().length;
3760            var input = $(this)[0];
3761            setTimeout(function(){
3762                input.setSelectionRange(inputLength, inputLength);
3763            }, 10);
3764        });
3765
3766        $obj.on("keydown", function(e){
3767            if ( e.keyCode == 13 ) {
3768                return false;
3769            }
3770        });
3771
3772        $obj.on('keyup change', function(){
3773            var val = $(this).val();
3774            var orig = $(this).data('originalvalue');
3775
3776            if ( val.length > 0 ) {
3777                if (! Validator.isPhone(val)) {
3778                    $(this).addClass('error');
3779                } else {
3780                    $(this).removeClass('error');
3781                    if (orig === undefined || $.trim(val.replace('x', '').replace(/_+$/, '')) != orig.replace('x', '')) {
3782                        $(this).addClass('dirty');
3783                        $(this).closest('form.dirtylisten').addClass('dirty');
3784                    } else {
3785                        $(this).removeClass('dirty');
3786                    }
3787                }
3788            } else {
3789                // if original value is not blank and new value is, mark as dirty
3790                if (orig.length != 0 && val.length == 0) {
3791                    $(this).addClass('dirty');
3792                    $(this).closest('form.dirtylisten').addClass('dirty');
3793                }
3794            }
3795
3796            if (! val.match(/\d/)) {
3797              $(this).removeClass('error');
3798            }
3799
3800            if ($scope.find('input.error').length > 0) {
3801              $('.leadfields_submit').removeClass('ui-state-active').addClass('ui-button-disabled ui-state-disabled').attr('disabled', true);
3802            } else {
3803              $('.leadfields_submit').removeClass('ui-button-disabled ui-state-disabled').addClass('ui-state-default').attr('disabled', false);
3804            }
3805
3806            return false;
3807        });
3808    }
3809}
3810
3811function leadFieldJS_copyButton($scope) {
3812    var $obj = $scope.find('.copybutton.feature-copy');
3813
3814    /* Load Txt JS (Copy Feature) if at least one field exists with copy feature */
3815    if ($obj && $obj.length) {
3816        $scope.find('.copybutton.feature-copy:not(.bound)').addClass('bound').button({
3817            icons: {
3818                primary: "ui-icon-copy"
3819            }
3820        }).on("click", function(e){
3821            e.preventDefault();
3822
3823            var $buttonObj = $(this);
3824            var copyFrom = $buttonObj.data('copyfrom') + "";
3825            var copyTo = $buttonObj.data('copyto') + "";
3826            var recordID = parseInt($buttonObj.attr('recid'));
3827
3828            if ( copyFrom.length && copyTo.length ) {
3829                copyFrom = copyFrom.split(",");
3830                copyTo = copyTo.split(",");
3831
3832                var copiedFieldsCount = 0;
3833                for( var i = 0; i < copyFrom.length; i++ ) {
3834                    var $fromObj = $('[name=lf_' + copyFrom[i] + ']');
3835                    var fromValue = $fromObj.val();
3836                    var $toObj = $('[name=lf_' + copyTo[i] + ']');
3837                    var toValue = $toObj.val();
3838                    var isFromSelect = $fromObj.is('select');
3839                    var isToSelect = $toObj.is('select');
3840                    var isFromText = $fromObj.is(':text') || $fromObj.is('textarea');
3841                    var isToText = $toObj.is(':text') || $toObj.is('textarea');
3842                    var isFromMasked = $fromObj.hasClass('masked-field');
3843                    var isToMasked = $toObj.hasClass('masked-field');
3844                    var fromLabel = $('label[for="' + $fromObj.attr('name') + '"]').text();
3845                    var toLabel = $('label[for="' + $toObj.attr('name') + '"]').text();
3846                    var fromCopyOfId = $fromObj.data('copy-of');
3847                    var fromCopiedValue = $fromObj.data('copied-value');
3848
3849                    // if from object is drop down & to is a text, then copy text
3850                    if (isFromSelect && isToText) {
3851                        fromValue = $fromObj.find('option:selected').text();
3852                    } else if ((isToSelect && isFromText) || (isToSelect && isFromSelect)) {
3853                        var $toOption = $toObj.find('option').filter(function () {
3854                            var spaceFix = fromValue.replace(/\s/g, "&nbsp;");
3855                            var thisVal  = $(this).html();
3856                            var thisTxt  = $(this).text();
3857
3858                            if (isToSelect && isFromSelect) {
3859                                return $fromObj.find('option:selected').text() == thisTxt;
3860                            }
3861
3862                            return thisVal == spaceFix || thisTxt == fromValue || thisVal == fromValue;
3863                        });
3864
3865                        if ($toOption !== undefined && $toOption.length) {
3866                            fromValue = $toOption.val()
3867                        }
3868                    }
3869
3870                    if ( !isNaN(recordID) && recordID > 1 ) {
3871                        $toObj = $('[name=lf_' + copyTo[i] + '_' + recordID + ']');
3872                        toValue = $toObj.val();
3873                    }
3874
3875                    // if ( toValue != fromValue )
3876                    // {
3877                    //     bChangeDetected = true;
3878                    // }
3879
3880                    // disallow  copying from a masked field to not masked
3881                    if (isFromMasked && !isToMasked) {
3882                        if (window.createErrorNotification instanceof Function) {
3883                            createErrorNotification(
3884                                'Error',
3885                                'Please unlock the '
3886                                + (fromLabel || 'secure')
3887                                + ' field before attempting to copy its value'
3888                                + (toLabel ? (' to ' + toLabel) : '')
3889                                + '.'
3890                            );
3891                        }
3892
3893                        // skip copying of this field
3894                        continue;
3895                    }
3896
3897                    // check if copying to a masked field
3898                    if (isToMasked) {
3899                        if (isFromMasked) {
3900                            // check if source field is a copy of another field
3901                            if (fromCopyOfId !== undefined) {
3902                                // check if source field has stored copied value
3903                                if (fromCopiedValue !== undefined) {
3904                                    // store value at the moment of copying to avoid unexpected value
3905                                    $toObj.attr('data-copied-value', fromCopiedValue);
3906                                }
3907
3908                                // if source field is a copy of another field, store source's parent field id
3909                                $toObj.attr('data-copy-of', fromCopyOfId);
3910                            } else {
3911                                // store source field id when copying from masked field
3912                                $toObj.attr('data-copy-of', copyFrom[i]);
3913                            }
3914                        } else {
3915                            // store source value when copying from not masked field
3916                            // store value at the moment of copying to avoid unexpected value
3917                            $toObj.attr('data-copied-value', fromValue);
3918                            $toObj.attr('data-copy-of', copyFrom[i]);
3919                        }
3920
3921                        $toObj.change();
3922                    } else {
3923                        $toObj.val(fromValue);
3924
3925                        if (fromValue != toValue) {
3926                            $toObj.change();
3927                        }
3928                    }
3929
3930                    copiedFieldsCount++;
3931
3932                    $toObj.addClass('dirty');
3933                }
3934                if (copiedFieldsCount) {
3935                    if (window.createSuccessNotification instanceof Function) {
3936                        createSuccessNotification(
3937                            'Success',
3938                            copiedFieldsCount + ' field' + (copiedFieldsCount > 1 ? 's' : '')
3939                            + ' w' + (copiedFieldsCount > 1 ? 'ere' : 'as') + ' copied successfully.')
3940                    }
3941                } else {
3942                    if (window.createInfoNotification instanceof Function) {
3943                        createInfoNotification('Info', 'No fields was copied successfully.')
3944                    }
3945                }
3946            }
3947
3948            return false;
3949        });
3950    }
3951}
3952
3953function leadFieldJS_txtSecure($scope) {
3954    var $obj = $scope.find('.type-txtsecure');
3955
3956    /* Load TxtSecure JS if at least one field exists */
3957    if ($obj && $obj.length) {
3958        $scope.find('.securefield').on('click', function() {
3959            var $thisObj     = $(this);
3960            var leadID       = parseInt($thisObj.data('leadid'));
3961            var fieldNameArr = $thisObj.attr('id').split('_');
3962
3963            if (! isNaN(leadID) && leadID > 0) {
3964                // Remove _security part
3965                fieldNameArr.pop();
3966
3967                var fieldName = fieldNameArr.join('_');
3968                var fieldId = fieldNameArr[1];
3969                var record = fieldNameArr.length > 2 ? fieldNameArr[2] : 1;
3970                var $elem = $scope.find('.type-txtsecure[name=' + fieldName + ']')
3971                var copyOfId = $elem.data('copy-of');
3972                var copiedValue = $elem.data('copied-value');
3973
3974                var unlockFieldFunc = function ($elem, value) {
3975                    $elem.val(value);
3976
3977                    if (! $elem.hasClass('disabled-field')) {
3978                        $elem.attr('readonly', false).attr('disabled', false);
3979                    }
3980
3981                    $elem.addClass('unlocked-field');
3982                    $elem.removeClass('masked-field');
3983                    $elem.removeAttr('data-copy-of');
3984                    $elem.removeData('copy-of');
3985                    $elem.removeAttr('data-copied-value');
3986                    $elem.removeData('copied-value');
3987
3988                    // Apply any mask after the field readonly property is removed
3989                    activateMaskClass($elem.parent());
3990                }
3991
3992                // handle unlocking copied field
3993                if (copyOfId) {
3994                    var sourceField = $scope.find('[name=lf_' + copyOfId + ']');
3995
3996                    if (!sourceField || !sourceField.length) {
3997                        if (window.createErrorNotification instanceof Function) {
3998                            createErrorNotification(
3999                                'Error',
4000                                'Cannot view source field.'
4001                            );
4002                        }
4003                        return;
4004                    }
4005
4006                    var sourceName = sourceField.attr('name').replace('lf_', '').split('_');
4007                    var sourceFieldId = sourceName[0];
4008                    var sourceRecord = sourceName.length > 1 ? sourceName[1] : 1;
4009                    var url = fieldId + '/' + sourceFieldId + '/' + sourceRecord;
4010
4011                    if (copiedValue !== undefined) {
4012                        // log access and show the copied value
4013                        $.get('/lead/secure-copying-log/' + leadID + '/' + url, function(data) {
4014                            if (data.success) {
4015                                unlockFieldFunc($elem, copiedValue);
4016                            }
4017
4018                        });
4019                    } else {
4020                        // log access and show the original value of "copy from" field
4021                        $.get('/lead/secure-copying-log/' + leadID + '/' + url, function(data) {
4022                            if (data.success) {
4023                                unlockFieldFunc($elem, data.value);
4024                            }
4025                        });
4026                    }
4027                } else {
4028                    var url = fieldId + '/' + record;
4029
4030                    $.get('/lead/securelog/' + leadID + '/' + url, function(data) {
4031                        unlockFieldFunc($elem, data);
4032                    });
4033                }
4034            }
4035        });
4036    }
4037}
4038
4039function leadFieldJS_jqDate($scope) {
4040    const $obj = $scope.find('.type-jqdate');
4041
4042    /* Load jQDate JS if at least one field exists */
4043    if ($obj && $obj.length) {
4044        $obj.each(function() {
4045            const $input          = $(this);
4046            const $pickerTrigger  = $input.siblings('.ui-datepicker-trigger');
4047            const dateValue       = $input.val();
4048            const dateFormat      = $input.hasClass('validateDate2') ? 'mm/yy' : 'mm/dd/yy';
4049
4050            const validationHandler = function (dateText, showError) {
4051                if (
4052                    dateText !== '__/____' &&
4053                    dateText !== '__/__/____' &&
4054                    (
4055                        dateFormat === 'mm/yy' && ! moment(dateText, 'MM/YYYY', true).isValid() ||
4056                        dateFormat === 'mm/dd/yy' && ! moment(dateText, 'MM/DD/YYYY', true).isValid()
4057                    )
4058                ) {
4059                    $pickerTrigger.attr({title: label, alt: label});
4060                    $input.datepicker('setDate', '');
4061
4062                    if (showError) {
4063                        $input.addClass('error');
4064                        createErrorNotification('Error', 'Invalid date format', 3000);
4065                    }
4066                } else if (showError) {
4067                    $input.removeClass('error');
4068                }
4069            }
4070
4071            const options = {
4072                dateFormat,
4073                beforeShow: function (i) {
4074                    $('#ui-datepicker-div').removeClass('hideDays');
4075                    if ($(i).attr('disabled') || $(i).attr('readonly')) {
4076                        return false;
4077                    }
4078                }
4079            };
4080
4081            if (dateFormat === 'mm/yy') {
4082                $('.ui-datepicker-calendar').hide();
4083                $input.mask('99/9999');
4084            } else {
4085                $input.mask('99/99/9999');
4086            }
4087
4088            let label = $input.siblings('label').html();
4089            if (! $input.attr('disabled') && ! $input.attr('readonly') && ! $input.attr('data-ignore-icon')) {
4090                options.showOn = 'both';
4091                options.buttonImage = '/images/icons/date.png';
4092                options.buttonImageOnly = true;
4093
4094                if(label?.length > 0) {
4095                    options.buttonText = label;
4096                }
4097            }
4098
4099            options.onClose = function (dateText) {
4100                validationHandler(dateText, true);
4101            }
4102
4103            if (dateFormat === 'mm/yy') {
4104                options.changeYear      = true;
4105                options.changeMonth     = true;
4106                options.showButtonPanel = true;
4107
4108                options.onChangeMonthYear = function (year, month, inst) {
4109                    const title = label + `: ${(inst.selectedMonth + 1).toString().padStart(2, 0)}/${inst.selectedYear}`;
4110
4111                    $pickerTrigger.attr({title, alt: title});
4112                    $input.val($.datepicker.formatDate('mm/yy', new Date(year, month - 1, 1)));
4113                };
4114
4115                options.beforeShow = function () {
4116                    $('#ui-datepicker-div').addClass('hideDays');
4117                    $input.trigger('keyup');
4118                };
4119
4120                $input.datepicker(options);
4121                $input.addClass('hideDays');
4122
4123                $(this).on('keyup', function () {
4124                    const $input    = $(this);
4125                    const dateValue = $(this).val();
4126                    debounce(
4127                        function () {
4128                            if (moment(dateValue, 'MM/YYYY', true).isValid()) {
4129                                const dateArray = dateValue.split('/');
4130                                const month     = (dateArray[0] ?? '').replace(/_/gi, '');
4131                                const year      = (dateArray[1] ?? '').replace(/_/gi, '');
4132                                const title     = label + `: ${dateValue}`;
4133
4134                                $input.datepicker('setDate', new Date(year, month, 0));
4135                                $pickerTrigger.attr({title, alt: title});
4136                            } else {
4137                                $pickerTrigger.attr({title: label, alt: label});
4138                            }
4139                        },
4140                        300
4141                    )();
4142                });
4143            }
4144
4145            if (dateFormat === 'mm/dd/yy') {
4146                $input.datepicker(options);
4147
4148                $input.on('change', function () {
4149                    const dateValue = $(this).val();
4150
4151                    $(this).datepicker('option', 'buttonText', label + (dateValue.length > 0 ? ': ' + dateValue : ''));
4152                    $(this).siblings('.ui-datepicker-trigger').tooltip();
4153                });
4154            }
4155
4156            validationHandler(dateValue, false);
4157        });
4158    }
4159}
4160
4161function leadFieldJS_selectRed($scope) {
4162    var $obj = $scope.find('select.type-selectred');
4163
4164    /* Load JS for SelectRed if at least one field exists */
4165    if ($obj && $obj.length) {
4166        $obj.on('change', function() {
4167            var value = $(this).find('option:selected').text();
4168
4169            $(this).removeClass('selectred selectgreen selectdarkgray');
4170
4171            if($(this).data('chosen')) $(this).data('chosen').container.removeClass('selectred selectgreen selectdarkgray');
4172
4173            if (value == 'Required') {
4174                $(this).addClass('selectred');
4175                if($(this).data('chosen')) $(this).data('chosen').container.addClass('selectred');
4176            } else if (value == 'Waived') {
4177                $(this).addClass('selectgreen');
4178                if($(this).data('chosen')) $(this).data('chosen').container.addClass('selectgreen');
4179            } else if (value == 'Power Pay') {
4180                $(this).addClass('selectdarkgray');
4181                if($(this).data('chosen')) $(this).data('chosen').container.addClass('selectdarkgray');
4182            }
4183        });
4184    }
4185}
4186
4187function updateChosenDDByName(name, option) {
4188    $('[name="' + name + '"]').val(option).trigger('chosen:updated')
4189}
4190
4191function leadFieldJS_googleMaps($scope) {
4192    if (typeof window.addAddressAutoCompleteTo === 'function') {
4193        $scope.find('.address-search-autocomplete').each(function () {
4194            var options      = $(this).data('options')
4195            var recordId     = parseInt($(this).data('recordid'))
4196            recordId         = isNaN(recordId) || ! recordId ? 1 : recordId
4197            options          = options && options.length ? options.split(',') : []
4198            var fields       = {};
4199            var recordSuffix = recordId > 1 ? ("_" + recordId) : ""
4200
4201            options.forEach(function (item) {
4202                var text = $('label[for="lf_' + item + recordSuffix + '"').text().toLowerCase()
4203
4204                if (text.indexOf('zip') !== -1 || text.indexOf('postal') !== -1) {
4205                    fields.zip = 'lf_' + item + recordSuffix
4206                } else if (text.indexOf('city') !== -1) {
4207                    fields.city = 'lf_' + item + recordSuffix
4208                } else if (text.indexOf('state') !== -1) {
4209                    fields.state = 'lf_' + item + recordSuffix
4210                } else {
4211                    if (fields.address === undefined) {
4212                        fields.address = 'lf_' + item + recordSuffix
4213                    } else {
4214                        fields.address2 = 'lf_' + item + recordSuffix
4215                    }
4216                }
4217            })
4218
4219            var data = {};
4220            if (Object.entries(fields).length) {
4221                data.fields = fields;
4222            }
4223
4224            window.addAddressAutoCompleteTo($(this), data, $scope)
4225        })
4226
4227        $scope.find('.address-search-autocomplete').on('focus', function() {
4228            if (window.searchStartedOnField !== undefined
4229                && window.searchStartedOnField[$(this).attr('name')] === true) {
4230                $(this).autocomplete("search", $(this).val())
4231            }
4232        })
4233    }
4234
4235    var $obj = $scope.find('.gSearchMap');
4236
4237    /* Load JS for GoogleMaps if at least one field exists */
4238    if ($obj && $obj.length) {
4239        $obj.on('click', function() {
4240            var options = $(this).data('options');
4241            var recordId = $(this).attr('id').split('_');
4242            recordId = recordId && recordId.length == 4 ? recordId.pop() : 1;
4243            options = options && options.length ? options.split(",") : '';
4244
4245            if (options.length) {
4246                var string = '';
4247                var recordFieldName = recordId > 1 ? '_' + recordId : '';
4248                for (var count = 0; count < options.length; count++) {
4249                    var $fieldObject = $("[name=lf_" + options[count] + recordFieldName + "]");
4250                    if ($fieldObject.is("select")) {
4251                        string += $fieldObject.find('option:selected').text() + " ";
4252                    } else {
4253                        string += $fieldObject.val()  + " ";
4254                    }
4255                }
4256
4257                $(this).attr("href", "http://maps.google.com/maps?q=" + encodeURIComponent(string));
4258            }
4259        });
4260    }
4261}
4262
4263/**
4264 * Finds field on lead form either with record id or without
4265 *
4266 * @param $scope
4267 * @param fieldId
4268 * @param recordId
4269 * @param isMultiple
4270 * @returns {*}
4271 */
4272function findFieldOnLeadForm($scope, fieldId, recordId, isMultiple) {
4273    if (isMultiple === true) {
4274        var value = '';
4275        $.each(fieldId, function(i, fId) {
4276            var $field = $scope.find('[name="lf_' + fId + recordId + '"]');
4277
4278            if (! $field || ! $field.length) {
4279                $field = $scope.find('[name="lf_' + fId + '"]');
4280            }
4281
4282            if ($field.val() !== undefined) {
4283                value += $field.val() + ' ';
4284            }
4285        });
4286
4287        return value.trim();
4288    }
4289
4290    var $field = $scope.find('[name="lf_' + fieldId + recordId + '"]');
4291
4292    if (! $field || ! $field.length) {
4293        $field = $scope.find('[name="lf_' + fieldId + '"]');
4294    }
4295
4296    return $field;
4297}
4298
4299function leadFieldJS_postcards($scope) {
4300    var $obj = $scope.find('.send-postcard');
4301
4302    if ($obj && $obj.length) {
4303        $obj.on('click', function() {
4304            var leadId          = $(this).data('leadid');
4305            var fieldId         = $(this).data('fieldid');
4306            var recordId        = $(this).data('recordid');
4307            var addressFields   = $(this).data('addressfields');
4308            var nameFields      = $(this).data('namefields');
4309            var companyField    = $(this).data('companyfield');
4310            var phoneField      = $(this).data('phonefield');
4311            var emailField      = $(this).data('emailfield');
4312            var fieldNameSuffix = recordId > 1 ? '_' + recordId : '';
4313
4314            var addressData = {
4315                company: findFieldOnLeadForm($scope, companyField, fieldNameSuffix, false).val(),
4316                name: findFieldOnLeadForm($scope, nameFields, fieldNameSuffix, true),
4317                phone: findFieldOnLeadForm($scope, phoneField, fieldNameSuffix, false).val(),
4318                email: findFieldOnLeadForm($scope, emailField, fieldNameSuffix, false).val()
4319            };
4320
4321            if (addressFields) {
4322                $.each(addressFields, function (fKey, fId) {
4323                    if (fId) {
4324                        var $field        = findFieldOnLeadForm($scope, fId, fieldNameSuffix, false);
4325                        addressData[fKey] = $field.val() !== undefined ? $field.val() : '';
4326
4327                        if ($field.is('select')) {
4328                            addressData[fKey] = $field.find('option:selected').text();
4329                        }
4330
4331                        if (fKey === 'state') {
4332                            addressData[fKey] = addressData[fKey].toUpperCase();
4333                        }
4334                    }
4335                });
4336            }
4337
4338            $.IRIS.helpers.postcard.dialogs.createPostCardDialog(
4339                {
4340                    title: 'Send Postcard',
4341                    baseUrl: '/manage/postcards/',
4342                    linkedToType: 'Leads',
4343                    linkedToId: leadId,
4344                },
4345                {
4346                    open: function(dialog) {
4347                        $.IRIS.helpers.postcard.dialogs.defaultAddressData = addressData;
4348
4349                        var $form = dialog.find('form');
4350
4351                        // try to find the address
4352                        $.IRIS.helpers.postcard.address.find(
4353                            addressData,
4354                            {
4355                                success: function(result) {
4356                                    if (result.addressId) {
4357                                        $form.find('select[name=postCardRecipient]').val(result.addressId);
4358                                    } else {
4359                                        setTimeout(function() {
4360                                            $form.find('select[name=postCardRecipient]').val('new').change();
4361                                        }, 100);
4362                                    }
4363                                }
4364                            }
4365                        );
4366                    }
4367                }
4368            );
4369        });
4370    }
4371}
4372
4373function leadFieldJS_packages($scope) {
4374    var $obj = $scope.find('.pricing_templates');
4375
4376    /* Load JS for Packages if at least one field exists */
4377    if ($obj && $obj.length) {
4378        $obj.on("change", function(e) {
4379            var $pricingObj   = $(this);
4380            var recordNum     = $pricingObj.attr("name").split("_");
4381            recordNum         = recordNum.length === 3 ? recordNum.pop() : 1;
4382            var recordId      = $pricingObj.closest('div[data-recordid]').data('recordid') || false;
4383            var $submitButton = $('.leadfields_submit');
4384            var $form         = $('#profileAccordion form[id^=lead]');
4385            var value         = $pricingObj.val();
4386
4387            if (value === '---') {
4388                return;
4389            }
4390            if (confirm("This action will overwrite the current values for this record. Do you want to proceed?")) {
4391                $.get("/lead/loadleadtemplate/" + value , function(data) {
4392                    const numberOfPricingFields = +(Object.keys(data).length - 1)
4393                    const fieldsString = numberOfPricingFields > 1 ? 'fields' : 'field';
4394                    if (numberOfPricingFields > 0) {
4395                        createSuccessNotification('Template Applied', `You successfully updated ${numberOfPricingFields} ${fieldsString}.`);
4396                    }
4397
4398                    // map template pricing format
4399                    var pricingFormat;
4400                    var leadTabs = $pricingObj.closest('.lead-tabs')
4401
4402                    if (recordId) {
4403                        pricingFormat = leadTabs.find("div[data-recordid=" + recordId + "] select.pricing_format");
4404                    } else {
4405                        pricingFormat = leadTabs.find("select.pricing_format");
4406                    }
4407
4408                    if (undefined !== data['pricing_format'] && pricingFormat.length) {
4409                        pricingFormat.find("option[value='" + data['pricing_format'] + "']").prop("selected", true);
4410                        pricingFormat.trigger('chosen:updated');
4411                    }
4412
4413                    // map fields from template
4414                    $.each(data, function(fieldId, value) {
4415                        var target      = $('[name="lf_'+ fieldId + '"]').closest('[role="tabpanel"]');
4416                        var isRecordSet = !! target.find('[data-recordid]').length;
4417                        var $fieldObj   = $("[name=lf_" + fieldId + "]");
4418
4419                        if (isRecordSet) {
4420                            if (recordId && recordNum > 1) {
4421                                var record = $('div[data-recordid=' + recordId + ']');
4422                                $fieldObj = record.find('[name=lf_' + fieldId + '_' + recordNum + ']');
4423                            }
4424                        }
4425
4426                        if ($fieldObj.is('input:checkbox')) {
4427                            value = value.toLowerCase();
4428                            if (value === true || value === 'true' || value === 'yes' || parseInt(value) === 1) {
4429                                $fieldObj.prop('checked', true).change();
4430                            } else if (value === false || value === 'false' || value === 'no' || parseInt(value) === 0) {
4431                                $fieldObj.prop('checked', false).change();
4432                            }
4433                        } else if($fieldObj.is('select')) {
4434                            var $valueFound = $fieldObj.find('option[value="' + htmlSpecialCharsJS(value) + '"]');
4435
4436                            if ($valueFound.length) {
4437                                $fieldObj.val(parseInt(value)).change();
4438                            } else {
4439                                // if not found by value, then look for text
4440                                $fieldObj.val(
4441                                    $fieldObj.find('option').filter(function () {
4442                                        return $(this).text().replace(/\s/gi, ' ').toLowerCase() === value.replace(/\s/gi, ' ').toLowerCase();
4443                                    }).val()
4444                                ).change();
4445                            }
4446                        } else {
4447                            $fieldObj.val(value).change();
4448                        }
4449                    });
4450
4451                    // rescan current form
4452                    $form.dirtyForms('rescan');
4453                    if ($form.dirtyForms('isDirty')) {
4454                        $submitButton.button('enable');
4455                    }
4456                }, "json");
4457            }
4458        });
4459    }
4460}
4461
4462// Encode/Decode values to Base64
4463var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(e){var t="";var n,r,i,s,o,u,a;var f=0;e=Base64._utf8_encode(e);while(f<e.length){n=e.charCodeAt(f++);r=e.charCodeAt(f++);i=e.charCodeAt(f++);s=n>>2;o=(n&3)<<4|r>>4;u=(r&15)<<2|i>>6;a=i&63;if(isNaN(r)){u=a=64}else if(isNaN(i)){a=64}t=t+this._keyStr.charAt(s)+this._keyStr.charAt(o)+this._keyStr.charAt(u)+this._keyStr.charAt(a)}return t},decode:function(e){var t="";var n,r,i;var s,o,u,a;var f=0;e=e.replace(/[^A-Za-z0-9+/=]/g,"");while(f<e.length){s=this._keyStr.indexOf(e.charAt(f++));o=this._keyStr.indexOf(e.charAt(f++));u=this._keyStr.indexOf(e.charAt(f++));a=this._keyStr.indexOf(e.charAt(f++));n=s<<2|o>>4;r=(o&15)<<4|u>>2;i=(u&3)<<6|a;t=t+String.fromCharCode(n);if(u!=64){t=t+String.fromCharCode(r)}if(a!=64){t=t+String.fromCharCode(i)}}t=Base64._utf8_decode(t);return t},_utf8_encode:function(e){e=e.replace(/rn/g,"n");var t="";for(var n=0;n<e.length;n++){var r=e.charCodeAt(n);if(r<128){t+=String.fromCharCode(r)}else if(r>127&&r<2048){t+=String.fromCharCode(r>>6|192);t+=String.fromCharCode(r&63|128)}else{t+=String.fromCharCode(r>>12|224);t+=String.fromCharCode(r>>6&63|128);t+=String.fromCharCode(r&63|128)}}return t},_utf8_decode:function(e){var t="";var n=0;var r=c1=c2=0;while(n<e.length){r=e.charCodeAt(n);if(r<128){t+=String.fromCharCode(r);n++}else if(r>191&&r<224){c2=e.charCodeAt(n+1);t+=String.fromCharCode((r&31)<<6|c2&63);n+=2}else{c2=e.charCodeAt(n+1);c3=e.charCodeAt(n+2);t+=String.fromCharCode((r&15)<<12|(c2&63)<<6|c3&63);n+=3}}return t}}
4464
4465/**
4466 * Creates a reschedule email dialog with given open function and success button
4467 *
4468 * @param $element
4469 * @param openFunc
4470 * @param closeFunc
4471 * @param successButton
4472 * @param cancelButton
4473 */
4474function createRescheduleEmailDialog($element, openFunc, closeFunc, successButton, cancelButton) {
4475    var defaultCancelButton = {
4476        text:  "Cancel",
4477        icons: {
4478            primary: 'ui-icon-trash'
4479        },
4480        click: function(){
4481            $(this).dialog('close');
4482        }
4483    };
4484
4485    cancelButton = cancelButton === undefined || !cancelButton ? defaultCancelButton : cancelButton;
4486
4487    return $element.dialog({
4488        autoOpen: false,
4489        modal: true,
4490        resizable: false,
4491        open: openFunc,
4492        beforeClose: closeFunc,
4493        buttons: {
4494            btn1: cancelButton,
4495            btn2: successButton
4496        }
4497    });
4498}
4499
4500function parseEmailHeader(headerEmail)
4501{
4502  var extract = { name: "", email: "" };
4503
4504  var emails = headerEmail.match(/[^@<\s]+@[^@\s>]+/g);
4505
4506  if (emails) {
4507    extract.email = emails[0];
4508  }
4509
4510  var names = headerEmail.split(/\s+/);
4511
4512  if (names.length > 1) {
4513    names.pop();
4514    extract.name = names.join(" ").replace(/"/g, "");
4515  }
4516
4517  return extract;
4518}
4519
4520/* check if passed in value is integer or anything else */
4521function isInt(x) {
4522    var y = parseInt(x);
4523    if ( isNaN(y) ) return false;
4524    return x == y && x.toString() == y.toString();
4525}
4526
4527function isFloat(x)
4528{
4529    var y = parseFloat(x);
4530    if (isNaN(y)) return false;
4531    return x == y && x.toString() == y.toString();
4532}
4533
4534var Validator = {
4535    /**
4536     * Is Email
4537     *
4538     * @param {string} email       Email
4539     * @param {boolean} permissive Permissive
4540     *
4541     * @return {boolean}
4542     */
4543    isEmail: function (email, permissive = false) {
4544        let regex = /[0-9a-z][.'\-_+&]*[0-9a-z]*[@][0-9a-z_-]+[.]+[0-9a-z]{2,}/i
4545
4546        if (permissive === true) {
4547            regex = /^[a-z0-9]+([.'\-_+&][a-z0-9]*)*@[a-z0-9]+([-.][a-z0-9]+)*\.[a-z0-9]+([-.][a-z0-9]+)*$/i
4548        }
4549
4550        return regex.test(email)
4551    },
4552    isPhone : function(phone) {
4553        return window.getPhoneField(phone, 'US', true, true) !== false;
4554    },
4555    isFloat : function (number) {
4556        var y = parseFloat(number);
4557        if (isNaN(y)) return false;
4558        return number == y && number.toString() == y.toString();
4559    },
4560    isInt : function (number) {
4561        var y = parseInt(number);
4562        if ( isNaN(y) ) return false;
4563        return number == y && number.toString() == y.toString();
4564    },
4565    isMerchantNumber : function (number) {
4566        /**
4567         * Checks if a given string is a valid merchant number
4568         * - only digits
4569         * - length from 1 to 18
4570         * - the first digit cannot be zero
4571         *
4572         * fyi: parseInt's approach fails on big integers (integer overflowing/rounding issues)
4573         */
4574        return /^[1-9]\d{0,17}$/.test(number);
4575    },
4576    isIPAddress : function (ip) {
4577        var $pattern = new RegExp(
4578            '^' +
4579            '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.' +
4580            '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.' +
4581            '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.' +
4582            '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)' +
4583            '$'
4584        );
4585
4586        return $pattern.test(ip);
4587    },
4588    onlyDigits: function (val) {
4589        var regex = /^[0-9]+$/;
4590        return regex.test(val);
4591    },
4592    isURL: function (url) {
4593        var pattern = new RegExp('^(https?:\\/\\/)?'+ // protocol
4594            '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name
4595            '((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address
4596            '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path
4597            '(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string
4598            '(\\#[-a-z\\d_]*)?$','i'); // fragment locator
4599        return !!pattern.test(url);
4600    }
4601};
4602
4603Date.prototype.pformat = function(format){
4604	var t = this;
4605	var parameters = {
4606		Y: function() {return t.getFullYear()},
4607		m: function() {return (t.getMonth()+1) < 10 ? '0' + (t.getMonth() + 1).toString() : t.getMonth() + 1;},
4608		d: function() {return t.getDate() < 10 ? '0' + t.getDate() : t.getDate();},
4609		H: function() {return t.getHours() < 10 ? '0' + t.getHours() : t.getHours();},
4610		h: function() {
4611				var h = t.getHours();
4612				h = h > 12 ? h - 12 : h;
4613				h = h < 10 ? '0' + h : h;
4614				h = h === '00' ? 12 : h;
4615				return h;
4616			},
4617		i: function() {return t.getMinutes() < 10 ? '0' + t.getMinutes() : t.getMinutes();},
4618		s: function() {return t.getSeconds() < 10 ? '0' + t.getSeconds() : t.getSeconds();},
4619        a: function() {return t.getHours() >= 12 ? 'pm' : 'am';},
4620        P: function() {
4621			var h = -1 * t.getTimezoneOffset() / 60;
4622			var sign = h < 0 ? '-' : '+';
4623			var hour = Math.floor(Math.abs(h));
4624			hour = hour < 12 ? '0' + hour : hour;
4625			var min = Math.floor(60 * Math.abs(h % hour));
4626            min = min < 12 ? '0' + min : min;
4627	        return sign + hour + ':' + min;
4628        },
4629		Z: function() {return t.getTimezoneOffset();}
4630	};
4631
4632	var result = [];
4633	var tmp = '';
4634	for (var q = 0; q < format.length; q++) {
4635		tmp = parameters[format[q]] === undefined ? format[q] : parameters[format[q]]();
4636		result.push(tmp);
4637	}
4638	return result.join('');
4639};
4640
4641var Hash = {
4642    hashes: {},
4643    init: function(){
4644        var hash = location.hash;
4645        if (hash === '') {
4646            return;
4647        }
4648        hash = hash.slice(1).replace(/^\//, '').split('/');
4649        for (var q = 0; q < hash.length; q+=2){
4650            Hash.hashes[hash[q]] = hash[q+1];
4651        }
4652    },
4653    add: function(key, value){
4654        Hash.hashes[key] = value;
4655    },
4656    set: function(key, value){
4657        Hash.clear();
4658        Hash.add(key, value);
4659    },
4660    clear: function(){
4661        Hash.hashes = {};
4662    },
4663    get: function(key){
4664        return Hash.hashes[key];
4665    },
4666    isset: function(key){
4667        return Hash.hashes.hasOwnProperty(key);
4668    },
4669    toString: function(){
4670        var result = [];
4671        for (var q in Hash.hashes) {
4672            if (Hash.isset(q)) {
4673                result.push(encodeURIComponent(q), encodeURIComponent(Hash.get(q)));
4674            }
4675        }
4676        return '/' + result.join('/');
4677    },
4678    apply: function(){
4679        var hash = Hash.toString();
4680        location.hash = '#' + hash;
4681    }
4682};
4683
4684if (!String.prototype.trim) {
4685    String.prototype.trim = function () {
4686        return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
4687    };
4688}
4689
4690function LeadErrors(formId)
4691{
4692
4693    var errorClass = 'lead-error';
4694
4695    var form = $(formId);
4696    form.on('focus', 'input, textarea, select, div.chosen-container, span.dummyErrorWrapper', clearError);
4697    form.on('focus', 'a.ui-tabs-anchor, h4.ui-accordion-header', clearError);
4698    form.closest('.lead-container').prev('h3.accordion-header').on('focus', clearError);
4699    var invalidElements = $();
4700    var invalidTabs = $();
4701    var clearAfterTimeout;
4702
4703    var selectEmptyValues = [
4704        /select/i,
4705        /^[-\s]*$/
4706    ];
4707
4708    this.isValid = function () {
4709        this.clearErrors();
4710        var requiredElements = form.find('.requiredLeadField');
4711        requiredElements.each(function () {
4712            switch (this.nodeName.toLowerCase()) {
4713                case 'select':
4714                    var el = $(this).next('div.chosen-container');
4715                    var selectedText = $(this).find(':selected').text();
4716
4717                    var isEmpty = false;
4718                    for (var q = 0; q < selectEmptyValues.length; q++) {
4719                        if (selectedText.match(selectEmptyValues[q])) {
4720                            isEmpty = true;
4721                            break;
4722                        }
4723                    }
4724
4725                    if (isEmpty) {
4726                        if (el.length) {
4727                            invalidElements = invalidElements.add(el);
4728                        } else {
4729                            var span = $(this).parent('.dummyErrorWrapper');
4730                            if (span.length === 0) {
4731                                span = $(this).wrap('<span class="dummyErrorWrapper"></span>');
4732                            }
4733                            span = $(this).parent('.dummyErrorWrapper');
4734                            invalidElements = invalidElements.add(span);
4735                        }
4736                    }
4737
4738                    break;
4739                case 'textarea':
4740                    if (this.value.trim() === '') {
4741                        invalidElements = invalidElements.add(this);
4742                    }
4743                    break;
4744                case 'input':
4745                default:
4746                    var type = this.getAttribute('type');
4747                    if (((type === 'text' || type === 'tel') && this.value.trim() === '') || (type === 'checkbox' && !this.checked)) {
4748                        invalidElements = invalidElements.add(this);
4749                    }
4750            }
4751        });
4752        return invalidElements.length === 0;
4753    };
4754    this.displayErrors = function (clearAfter) {
4755        var $firstElement = invalidElements.first();
4756        // If we want to auto open the tab uncomment the below line
4757        // $('a[href="#' + invalidElements.first().closest('.ui-tabs-panel').attr('id') + '"]').click();
4758        if ($('form#Lead').length) {
4759            $('html, body').animate({
4760                scrollTop: $firstElement.offset().top - 100
4761            }, 1500, function(){
4762                if ($firstElement.hasClass('chosen-container')) {
4763                    $firstElement.siblings('select.useChosen').trigger('chosen:activate');
4764                } else {
4765                    $firstElement.focus();
4766                }
4767            });
4768        }
4769
4770        invalidElements.addClass(errorClass);
4771        if (clearAfter) {
4772            clearTimeout(clearAfterTimeout);
4773            clearAfterTimeout = setTimeout(this.clearErrors, clearAfter);
4774        }
4775        invalidElements.each(function () {
4776            var $div = $(this).closest('div.ui-tabs-panel.ui-widget-content');
4777            invalidTabs = invalidTabs.add($('li[aria-controls=' + $div.attr('id') + ']:not(.ui-state-active) > a'));
4778            // merchant profile accordion headers
4779            var header = $(this).closest('.lead-container').prev('h3.ui-accordion-header:not(.ui-state-active)');
4780            invalidTabs = invalidTabs.add(header);
4781            // record set accordion headers
4782            header = $(this).closest('.tabbedform').prev('h4.ui-accordion-header:not(.ui-state-active)');
4783            invalidTabs = invalidTabs.add(header);
4784        });
4785        invalidTabs.addClass(errorClass);
4786    };
4787    this.clearErrors = function () {
4788        invalidElements.removeClass(errorClass);
4789        invalidElements = $();
4790        invalidTabs.removeClass(errorClass);
4791        invalidTabs = $();
4792    };
4793
4794    this.markFieldsInvalid = function(fields) {
4795        fields.forEach((id) => {
4796            let name = String(id);
4797
4798            if (!name.startsWith('lf_')) {
4799                name = `lf_${name}`
4800            }
4801
4802            const $input = form.find(`[name=${name}]`)
4803
4804            if ($input.length) {
4805                invalidElements = invalidElements.add($input)
4806            }
4807        })
4808    }
4809
4810    function clearError()
4811    {
4812        $(this).removeClass(errorClass);
4813    }
4814}
4815
4816var CRMAssistant = {
4817    actions: [
4818        '#ticketCreated',
4819        '#emailGmailSynced',
4820        '#emailOutlookSynced',
4821        '#postTokenCreated',
4822        '#ivrGreetingUploaded',
4823        '#leadCreated',
4824        '#userCreated',
4825        '#emailTemplateCreated',
4826        '#smsTemplateCreated'
4827    ],
4828    action: function (action) {
4829        if (this.actions.indexOf(action) != -1) {
4830            this.resetUrl(action);
4831            this.process(action.replace('#', ''));
4832        }
4833    },
4834
4835    resetUrl: function (action) {
4836        history.replaceState({page: action}, document.title, location.pathname + location.search);
4837    },
4838
4839    process: function (event) {
4840        $('body').append($('<div id="' + event + '">'));
4841    },
4842
4843    sendMessage: function(message) {
4844        localStorage.setItem('assistant_event', message);
4845        localStorage.removeItem('assistant_event');
4846    },
4847
4848    getUrl: function () {
4849        let url = 'https://cdn.walkme.com/users/67257628812045968475c65913d82e1c/walkme_67257628812045968475c65913d82e1c_https.js';
4850        if (window.globalParameters.env !== 'production' && window.globalParameters.env !== 'demo') {
4851            url = 'https://cdn.walkme.com/users/67257628812045968475c65913d82e1c/test/walkme_67257628812045968475c65913d82e1c_https.js';
4852        }
4853        return url;
4854    },
4855
4856    load: function() {
4857        var assistant = document.createElement('script');
4858        assistant.type = 'text/javascript';
4859        assistant.async = true;
4860        assistant.src = this.getUrl();
4861        var s = document.getElementsByTagName('script')[0];
4862        s.parentNode.insertBefore(assistant, s);
4863        window._walkmeConfig = {smartLoad:true};
4864    },
4865
4866    setVars: function(vars) {
4867        window.__wm_params = vars;
4868    },
4869
4870    init: function (vars) {
4871        this.setVars(vars);
4872        if (! vars.disableWalkMe) {
4873            this.load();
4874        }
4875
4876        $(document).on('assistant_event', function (e, data) {
4877            CRMAssistant.process(data.event);
4878        });
4879
4880        $(window).on('storage', function (e) {
4881            if (e.originalEvent.key == 'assistant_event') {
4882                CRMAssistant.process(e.originalEvent.newValue);
4883            }
4884        });
4885
4886        this.action(location.hash);
4887    }
4888};
4889
4890var LiveChat = {
4891
4892    pushButton: function() {
4893        $('#wrap .cd-top').addClass('push-left-chat');
4894    },
4895
4896    setVars: function(liveChatParams) {
4897        window.__lc = window.__lc || {};
4898        window.__lc.license = liveChatParams.license;
4899
4900        window.__lc.params = [];
4901
4902        if (liveChatParams.isMerchant) {
4903            window.__lc.params.push({ name: 'DBA', value: liveChatParams.dba });
4904            window.__lc.params.push({ name: 'MID', value: liveChatParams.mid });
4905        }
4906
4907        window.__lc.params.push({ name: 'Client', value: liveChatParams.chatClientName });
4908        window.__lc.params.push({ name: 'Client Skeleton', value: liveChatParams.skeleton });
4909        window.__lc.params.push({ name: 'First Name', value: liveChatParams.firstName });
4910        window.__lc.params.push({ name: 'Last Name', value: liveChatParams.lastName });
4911        window.__lc.params.push({ name: "User's Phone Number", value: liveChatParams.phone });
4912        window.__lc.params.push({ name: 'User Class', value: liveChatParams.userClass });
4913        window.__lc.params.push({ name: 'Email', value: liveChatParams.email });
4914
4915        window.__lc.params.push({ name: 'Timezone', value: liveChatParams.timezone });
4916        window.__lc.params.push({ name: 'Creation Date', value: liveChatParams.creationDate });
4917
4918        window.__lc.visitor = {
4919            name: (liveChatParams.firstName + ' ' + liveChatParams.lastName),
4920            email: liveChatParams.email
4921        };
4922    },
4923
4924    load: function() {
4925        var lc = document.createElement('script'); lc.type = 'text/javascript'; lc.async = true;
4926        lc.src = ('https:' == document.location.protocol ? 'https://' : 'http://') + 'cdn.livechatinc.com/tracking.js';
4927        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(lc, s);
4928    },
4929
4930    init: function(liveChatParams) {
4931        if (liveChatParams.license) {
4932            this.pushButton();
4933            this.setVars(liveChatParams);
4934            this.load();
4935        }
4936    }
4937};
4938
4939var InactivityTimer = {
4940
4941    isUserLoggedIn: 0,
4942
4943    inactivity_flag_last: 0,
4944
4945    inactivitySeconds: 1,
4946
4947    timer: null,
4948
4949    resetInactivity: function () {
4950        // just loaded or activity detected
4951        setCookie('inactivity_flag', 0);
4952        this.inactivity_flag_last = 0;
4953        this.inactivitySeconds = 0;
4954
4955        // hide warn dialog
4956        $('.darkwin').css({'display': 'none'});
4957        $('#idle_timeout').removeClass('window-open');
4958    },
4959
4960    warnInactivity: function () {
4961        if (this.isUserLoggedIn > 0) {
4962            // idle
4963            setCookie('inactivity_flag', 1);
4964            this.inactivity_flag_last = 1;
4965
4966            // show dialog
4967            $('.darkwin').css({'display': 'block'});
4968            $('#idle_timeout').addClass('window-open');
4969        }
4970    },
4971
4972    expireInactivity: function () {
4973        // expired, logged out
4974        setCookie('inactivity_flag', 2);
4975        this.inactivity_flag_last = 2;
4976
4977        // logout
4978        window.bChangeDetected = false;
4979        window.location = '/index/logoutExpired';
4980
4981        if (this.timer) {
4982            clearTimeout(this.timer);
4983        }
4984    },
4985
4986    pingInactivity: function () {
4987        $.ajax({
4988            type: 'post',
4989            url: '/ajax/pingRequest',
4990            async: false,
4991            dataType: 'json',
4992            success: function (response) {
4993                if (response && response.result == 1) {
4994                    // activity detected
4995                    InactivityTimer.resetInactivity();
4996                } else {
4997                    // not logged
4998                    InactivityTimer.expireInactivity();
4999                }
5000            },
5001            error: function () {
5002                InactivityTimer.expireInactivity();
5003            }
5004        });
5005    },
5006
5007    subscribeToEvents: function () {
5008        $('#idle_timeout .window-close, .darkwin, #idle_timeout .continue-btn').on('click', function () {
5009            // activity detected, reset timer
5010            InactivityTimer.inactivitySeconds = 0;
5011
5012            // send ping
5013            InactivityTimer.pingInactivity();
5014        });
5015
5016        $('.win-close-btn', '#idle_timeout').on('click', function () {
5017            // expired, log out
5018            setCookie('inactivity_flag', 2);
5019
5020            // logout
5021            window.bChangeDetected = false;
5022            window.location = '/index/logoutManual';
5023        });
5024    },
5025
5026    init: function (isUserLoggedIn, expireUserSession) {
5027        this.isUserLoggedIn = isUserLoggedIn;
5028
5029        if (expireUserSession > 0) {
5030            if (isUserLoggedIn > 0) {
5031                setCookie('inactivity_flag', 0);
5032                this.timer = setInterval(function () {
5033                    // check Dialer activity
5034                    if (
5035                        typeof isConnected == 'function' && isConnected() &&
5036                        (InactivityTimer.inactivity_flag_last != 0 || InactivityTimer.inactivitySeconds > 30)
5037                    ) {
5038                        // activity detected and warning exists
5039                        InactivityTimer.inactivitySeconds = 0;
5040                        InactivityTimer.pingInactivity();
5041                    }
5042
5043                    // get expiration time every minute
5044                    if (InactivityTimer.inactivitySeconds >= 60) {
5045                        $.ajax({
5046                            type: 'get',
5047                            url: '/ajax/getExpirationTime',
5048                            async: false,
5049                            dataType: 'json',
5050                            success: function (response) {
5051                                if (response && response.result) {
5052                                    if (response.result == 'warning') {
5053                                        // to avoid abusing server with queries each second after 60
5054                                        InactivityTimer.inactivitySeconds = 0;
5055                                        InactivityTimer.warnInactivity();
5056                                    } else if (response.result == 'logout') {
5057                                        InactivityTimer.expireInactivity();
5058                                    } else if (response.result == 'reset') {
5059                                        InactivityTimer.resetInactivity();
5060                                    } else if (response.result == 'not_set') {
5061                                    } else {
5062                                        InactivityTimer.expireInactivity();
5063                                    }
5064                                } else {
5065                                    InactivityTimer.expireInactivity();
5066                                }
5067                            },
5068                            error: function () {
5069                                InactivityTimer.expireInactivity();
5070                            }
5071                        });
5072                    }
5073
5074                    // other tab events
5075                    var inactivity_flag = parseInt(getCookie('inactivity_flag'));
5076                    if (InactivityTimer.inactivity_flag_last != inactivity_flag) {
5077                        InactivityTimer.inactivity_flag_last = inactivity_flag;
5078                        if (inactivity_flag == 0) {
5079                            InactivityTimer.resetInactivity();
5080                        } else if (inactivity_flag == 1) {
5081                            InactivityTimer.warnInactivity();
5082                        } else if (inactivity_flag == 2) {
5083                            InactivityTimer.expireInactivity();
5084                        } else {
5085                            InactivityTimer.expireInactivity();
5086                        }
5087                    }
5088
5089                    InactivityTimer.inactivitySeconds++;
5090                }, 1000);
5091
5092                // key press activity
5093                $(document).on('keypress click', function () {
5094                    // if inactivity timer is > 30 seconds and keyboard activity detected - send "ping" and reset timer
5095                    if (InactivityTimer.inactivitySeconds > 30 || InactivityTimer.inactivity_flag_last != 0) {
5096                        InactivityTimer.inactivitySeconds = 0;
5097                        InactivityTimer.pingInactivity();
5098                    }
5099                });
5100            } else {
5101                // user not logged
5102                // any open tab will logout
5103                setCookie('inactivity_flag', 2);
5104            }
5105        }
5106
5107        this.subscribeToEvents();
5108    }
5109};
5110
5111function parseEmailHeader(headerEmail) {
5112    var extract = {name: "", email: ""};
5113
5114    var emails = headerEmail.match(/(.*)<([^>]+)>/);
5115
5116    if (emails) {
5117        extract.email = emails[2];
5118        extract.name = emails[1].trim().replace(/['"]+/g, '')
5119    } else {
5120        extract.email = headerEmail;
5121    }
5122
5123    return extract;
5124}
5125
5126/**
5127 * Submits form via ajax
5128 * @param $form
5129 * @param successCallback
5130 * @param params
5131 */
5132function ajaxSubmit($form, successCallback, params) {
5133
5134    var additionalParams = '';
5135    if (params !== undefined) {
5136        $.each(params, function(key, value) {
5137            additionalParams += '&' + key + '=' + value;
5138        });
5139    }
5140
5141    ajaxRequest({
5142        ajaxOptions: {
5143            type: $form.attr('method'),
5144            url: $form.attr('action'),
5145            data: $form.serialize() + additionalParams,
5146        },
5147        loaderOptions: {
5148            title: 'Saving',
5149            message: 'Saving, please wait...'
5150        }
5151    }, function(result) {
5152        createSuccessNotification('Saved', result.msg);
5153
5154        successCallback(result);
5155    });
5156}
5157
5158/**
5159 * Ajax request
5160 *
5161 * @param options
5162 * @param successCallback
5163 * @param errorCallback
5164 */
5165function ajaxRequest(options, successCallback, errorCallback) {
5166    var loader = null;
5167
5168    // do we need to display known validation errors using notifications?
5169    const showValidationErrors = typeof options.showValidationErrors === 'boolean'
5170        ? options.showValidationErrors
5171        : true;
5172
5173    var ajaxOptions = $.extend(
5174        {
5175            type: 'get',
5176            dataType: options.ajaxOptions.dataType !== undefined ? options.ajaxOptions.dataType : 'json',
5177            beforeSend: function() {
5178                if (typeof options.loaderOptions !== 'undefined') {
5179                    var loaderTitle   = options.loaderOptions.title !== undefined ? options.loaderOptions.title : 'Loading';
5180                    var loaderMessage = options.loaderOptions.message !== undefined ? options.loaderOptions.message : 'Loading, please wait...';
5181
5182                    loader = loaderDialog(loaderTitle, loaderMessage);
5183                }
5184
5185                if (typeof options.beforeSend === 'function') {
5186                    options.beforeSend();
5187                }
5188            },
5189            complete: function(response) {
5190                if (loader !== null) {
5191                    loader.close();
5192                }
5193
5194                if (options.ajaxOptions.dataType === 'html') {
5195                    if (typeof successCallback === 'function') {
5196                        successCallback(response.responseText);
5197                    }
5198                } else {
5199                    var result = response.responseJSON;
5200                    var error  = '';
5201                    var failed = false;
5202
5203                    if (result !== undefined) {
5204                        if (result.code !== undefined) {
5205                            if (result.code > 0) {
5206                                if (typeof successCallback === 'function') {
5207                                    successCallback(result);
5208                                }
5209                            } else {
5210                                error  = result.msg;
5211                                failed = true;
5212                            }
5213                        } else {
5214                            if (result.errors !== undefined) {
5215                                $.each(result.errors, function (i, msgs) {
5216                                    error = msgs[0];
5217                                    return false;
5218                                });
5219                            } else if (result.message !== undefined) {
5220                                error = result.message;
5221                            } else {
5222                                error = 'Internal server error.';
5223                            }
5224
5225                            failed = true
5226                        }
5227                    } else {
5228                        error = 'Internal server error.';
5229                    }
5230
5231                    if (error && error.length) {
5232                        if (showValidationErrors) {
5233                            createErrorNotification('Error', htmlSpecialCharsJS(error));
5234                        }
5235
5236                        failed = true;
5237                    }
5238
5239                    if (failed && typeof errorCallback === 'function') {
5240                        errorCallback(result);
5241                    }
5242                }
5243            }
5244        },
5245        options.ajaxOptions
5246    );
5247
5248    $.ajax(ajaxOptions);
5249}
5250
5251/**
5252 * Initializes buttons with icons
5253 * @param buttons
5254 * @param $container
5255 */
5256function initButtonsWithIcons(buttons, $container) {
5257    $.each(buttons, function(selector, iconName) {
5258        var $btn = $container !== undefined ? $container.find(selector) : $(selector);
5259
5260        $btn.button({
5261            icons: {
5262                primary: 'ui-icon-' + iconName
5263            }
5264        })
5265    });
5266}
5267
5268/**
5269 * Will return month in string format
5270 *
5271 * @param {int} monthNumber
5272 * @returns {string}
5273 */
5274function getMonthName(monthNumber) {
5275    var names = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "
5275November", "December" ];
5276
5277    return names[monthNumber - 1];
5278}
5279
5280/**
5281 * Makes data chunks
5282 *
5283 * @param {Array} data
5284 * @param {int} chunkSize
5285 *
5286 * @returns {Array} Chunks
5287 */
5288function makeDataChunks(data, chunkSize) {
5289    var start  = 0;
5290    var length = data.length;
5291    var chunks = [];
5292    chunks.push(data.slice(start, start + chunkSize));
5293    while ((start + chunkSize) < length) {
5294        start = start + chunkSize;
5295        chunks.push(data.slice(start, start + chunkSize));
5296    }
5297
5298    return chunks;
5299}
5300
5301/**
5302 * Capitalize first letter
5303 *
5304 * @param string
5305 *
5306 * @returns {string}
5307 */
5308function capitalizeFirstLetter(string) {
5309    return string.charAt(0).toUpperCase() + string.slice(1);
5310}
5311/**
5312 * Validates $ amount
5313*
5314 * @param string
5315 * @param allowEmpty
5316 * @returns {*}
5317 */
5318function validateAmount(string, allowEmpty) {
5319    if (allowEmpty && allowEmpty == true && (!string || (string && string.length == 0))) {
5320        return -2;
5321    }
5322    if (!validateLength(string, 1, 13)) {
5323        return false;
5324    }
5325    if (string <= 0) {
5326        return -1;
5327    }
5328    var regexp = /^\d{1,10}(\.\d{1,2})?$/;
5329    //validate that it contains only digits and might be decimal
5330    return regexp.test(string) && string > 0;
5331}
5332
5333/**
5334 * Validates length of string with min/max
5335 *
5336 * @param string
5337 * @param minLength
5338 * @param maxLength
5339 * @returns {boolean}
5340 */
5341function validateLength(string, minLength, maxLength) {
5342    return string !== undefined && string.length >= minLength && string.length <= maxLength;
5343}
5344
5345/**
5346 * Sets button icon
5347 *
5348 * @param $element
5349 * @param ui_icon
5350 * @param custom_icon
5351 */
5352function setButtonIcon($element, ui_icon, custom_icon) {
5353    var icon = ui_icon ? 'ui-icon-' + ui_icon : custom_icon;
5354
5355    $element.button({
5356        icons: {
5357            primary: icon
5358        }
5359    });
5360}
5361
5362/**
5363 * Validates CC info
5364 *
5365 * @returns boolean false if validation fails, list of validated fields otherwise
5366 */
5367function validateCCInfo($ccForm)
5368{
5369    var isValid = true;
5370    var fields  = {};
5371
5372    $ccForm.find('input[type=text]:not(.chosen-search-input), select').each(function() {
5373        var $element    = $(this);
5374        var isDropDown  = $element.is('select');
5375        var id          = $element.attr('id');
5376        var fieldName   = $element.attr('name');
5377        var labelText   = $ccForm.find('label[for=' + id + ']').html();
5378        var value       = $element.val();
5379        var valueLength = value && value !== undefined ? value.length : 0;
5380        var isRequired  = $element.data('required') == true;
5381        var minLength   = parseInt($element.attr('minlength'));
5382        var maxLength   = parseInt($element.attr('maxlength'));
5383        var digitsOnly  = $element.data('digitsonly') == true;
5384
5385        // if its year field, is label should be the Expire Date
5386        if (id == 'ddcardExpireYear') {
5387            labelText = $ccForm.find('label[for=ddcardExpireMonth]').html();
5388        }
5389
5390        labelText = labelText ? labelText.replace('<span class="required-field">*</span>
5390', '') : '';
5391
5392        if (isRequired && (! valueLength || (isDropDown && (! value || value == '0')))) {
5393            createErrorNotification(
5394                'Error',
5395                'Please enter "' + htmlSpecialCharsJS(labelText) + '".'
5396            );
5397
5398            isValid = false;
5399
5400            return false;
5401        }
5402
5403        if (! isNaN(minLength)) {
5404            if (valueLength < minLength) {
5405                createErrorNotification(
5406                    'Error',
5407                    labelText + ' must be at least '
5408                    + minLength + ' characters long.'
5409                );
5410
5411                isValid = false;
5412
5413                return false;
5414            }
5415        }
5416
5417        if (! isNaN(maxLength)) {
5418            if (valueLength > maxLength) {
5419                createErrorNotification(
5420                    'Error',
5421                    labelText + ' can not have more than  '
5422                    + maxLength + ' characters.'
5423                );
5424
5425                isValid = false;
5426
5427                return false;
5428            }
5429        }
5430
5431        if (digitsOnly && ! /^[0-9]+$/.test(value)) {
5432            // if the first 10 characters are asterix, we are not making a change so its fine
5433            if (! isDropDown && value.substring(0, 10) == '**********' && /^[0-9]+$/.test(value.substring(12))) {
5434                fields[fieldName] = value;
5435
5436                return true;
5437            }
5438
5439            // if its a drop down & value is * means we are not making a change so its fine
5440            if (isDropDown && value == '*') {
5441                fields[fieldName] = value;
5442
5443                return true;
5444            }
5445
5446            createErrorNotification(
5447                'Error',
5448                labelText + ' can only contain digits.'
5449            );
5450
5451            isValid = false;
5452
5453            return false;
5454        }
5455
5456        fields[fieldName] = value;
5457    });
5458
5459    return isValid ? fields : false;
5460}
5461
5462/**
5463 * Gets scheduler data from the given container
5464 * @param $container
5465 * @returns {*}
5466 */
5467function getSchedulerData($container) {
5468    var startOn         = $container.find('input[name=startOn]').val();
5469    var repeatFrequency = parseInt($container.find('input[name=repeatFrequency]').val());
5470    var repeatType      = $container.find('select[name=repeatOn]').val();
5471    var repeatSpecific  = [];
5472    var endType         = parseInt($container.find('input[name=endType]:checked').val());
5473    var endSpecific     = '';
5474
5475    if (isNaN(repeatFrequency) || ! repeatFrequency) {
5476        createErrorNotification('Error', 'Please enter the repeat frequency.');
5477
5478        return false;
5479    }
5480
5481    if (repeatType === 'week') {
5482        $container.find('.repeat-on-weekly input.repeat-on:checked').each(function() {
5483            repeatSpecific.push($(this).val());
5484        });
5485
5486        if (! repeatSpecific.length) {
5487            createErrorNotification('Error', 'Please select one day of the week.');
5488
5489            return false;
5490        } else if (repeatSpecific.length > 1) {
5491            createErrorNotification('Error', 'Please select only one day of the week.');
5492
5493            return false;
5494        }
5495    }
5496
5497    if (endType === 1) {
5498        endSpecific = $container.find('input[name=endsOn]').val();
5499
5500        if (! endSpecific || ! endSpecific.length) {
5501            createErrorNotification('Error', 'Please select end date.');
5502
5503            return false;
5504        }
5505    } else if (endType === 2) {
5506        endSpecific = parseInt($container.find('input[name=endsAfter]').val());
5507
5508        if (isNaN(endSpecific) || ! endSpecific) {
5509            createErrorNotification('Error', 'Please enter a valid number of occurrences.');
5510
5511            return false;
5512        }
5513    }
5514
5515    return {
5516        start: startOn,
5517        repeat: {
5518            amount: repeatFrequency,
5519            type: repeatType,
5520            params: repeatSpecific
5521        },
5522        end: {
5523            type: endType,
5524            value: endSpecific
5525        }
5526    };
5527}
5528
5529/**
5530 * Sets scheduler data
5531 * @param $container
5532 * @param schedulerData
5533 */
5534function setSchedulerData($container, schedulerData) {
5535    schedulerData = schedulerData['scheduler'] !== undefined ? schedulerData['scheduler'] : schedulerData;
5536
5537    if (schedulerData['start'] !== undefined) {
5538        $container.find('input[name=startOn]').val(schedulerData['start']);
5539    }
5540
5541    if (schedulerData['repeat'] !== undefined) {
5542        $container.find('select[name=repeatOn]').val(schedulerData['repeat']['type']);
5543        $container.find('input[name=repeatFrequency]').val(schedulerData['repeat']['amount']);
5544
5545        if (schedulerData['repeat']['type'] === 'week') {
5546            $.each(schedulerData['repeat']['params'], function(i, item) {
5547                $container.find('input[name="repeatOnWeekly[]"][value=' + item + ']').prop('checked', true);
5548            });
5549        }
5550    }
5551
5552    if (schedulerData['end'] !== undefined) {
5553        $container.find('input[name=endType][value=' + schedulerData['end']['type'] + ']').prop('checked', true);
5554
5555        if (schedulerData['end']['type'] === '1') {
5556            $container.find('input[name=endsOn]').val(schedulerData['end']['value']);
5557        } else if (schedulerData['end']['type'] === '2') {
5558            $container.find('input[name=endsAfter]').val(schedulerData['end']['value']);
5559        }
5560    }
5561}
5562
5563/**
5564 * Get human file size string
5565 *
5566 * @param {int} size
5567 * @param {int} decimals Num decimals to show, default 2
5568 *
5569 * @returns {string} Human file size string
5570 */
5571function humanFileSize(size, decimals)
5572{
5573    if (0 === size) {
5574        return '0 B';
5575    }
5576
5577    decimals = typeof decimals !== 'undefined'? decimals : 2;
5578    var i = Math.floor( Math.log(size) / Math.log(1024) );
5579
5580    return (size / Math.pow(1024, i)).toFixed(decimals) + ' ' + ['B', 'KB', 'MB', 'GB', 'TB'][i];
5581}
5582
5583/**
5584 * Get alias for file icon
5585 *
5586 * @param {string} fileExtension File extension
5587 *
5588 * @returns {string} Icon alias
5589 */
5590function getFileIcon(fileExtension)
5591{
5592    var icon;
5593
5594    switch (fileExtension) {
5595        case 'xls':
5596        case 'xlsx':
5597        case 'ods':
5598            icon = 'xls_icon';
5599            break;
5600        case 'csv':
5601            icon = 'icon_csv';
5602            break;
5603        case 'pdf':
5604            icon = 'page_white_acrobat';
5605            break;
5606        case 'rtf':
5607        case 'doc':
5608        case 'docx':
5609        case 'odt':
5610            icon = 'report_word';
5611            break;
5612        case 'png':
5613        case 'jpg':
5614        case 'jpeg':
5615        case 'gif':
5616        case 'tif':
5617        case 'bmp':
5618        case 'tiff':
5619        case 'eps':
5620            icon = 'report_picture';
5621            break;
5622        case 'ppt':
5623            icon = 'table_multiple';
5624            break;
5625        default:
5626            icon = 'report';
5627    }
5628
5629    return icon;
5630}
5631
5632if (!Array.prototype.includes) {
5633    Object.defineProperty(Array.prototype, 'includes', {
5634        value: function(searchElement, fromIndex) {
5635
5636            if (this == null) {
5637                throw new TypeError('"this" is null or not defined');
5638            }
5639
5640            // 1. Let O be ? ToObject(this value).
5641            var o = Object(this);
5642
5643            // 2. Let len be ? ToLength(? Get(O, "length")).
5644            var len = o.length >>> 0;
5645
5646            // 3. If len is 0, return false.
5647            if (len === 0) {
5648                return false;
5649            }
5650
5651            // 4. Let n be ? ToInteger(fromIndex).
5652            //    (If fromIndex is undefined, this step produces the value 0.)
5653            var n = fromIndex | 0;
5654
5655            // 5. If n ≥ 0, then
5656            //  a. Let k be n.
5657            // 6. Else n < 0,
5658            //  a. Let k be len + n.
5659            //  b. If k < 0, let k be 0.
5660            var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
5661
5662            function sameValueZero(x, y) {
5663                return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y));
5664            }
5665
5666            // 7. Repeat, while k < len
5667            while (k < len) {
5668                // a. Let elementK be the result of ? Get(O, ! ToString(k)).
5669                // b. If SameValueZero(searchElement, elementK) is true, return true.
5670                if (sameValueZero(o[k], searchElement)) {
5671                    return true;
5672                }
5673                // c. Increase k by 1.
5674                k++;
5675            }
5676
5677            // 8. Return false
5678            return false;
5679        }
5680    });
5681}
5682
5683/**
5684 * Linked to object helper
5685 * @param linkedTo
5686 * @returns string
5687 */
5688function linkedToObjectHelper(linkedTo) {
5689    if (! linkedTo || linkedTo['name'] === undefined || linkedTo['icon'] === undefined
5690        || linkedTo['type'] === undefined || linkedTo['url'] === undefined || ! linkedTo['type']) {
5691        return '';
5692    }
5693
5694    var linkedToUrl  = linkedTo['url'];
5695    var linkedToIcon = linkedTo['icon'];
5696    var linkedToName = htmlSpecialCharsJS(linkedTo['name']);
5697
5698    if (linkedToUrl) {
5699        linkedToUrl = '<a href="' + htmlSpecialCharsJS(linkedToUrl) + '" target="_blank">' + linkedToName + '</a>';
5700    } else {
5701        linkedToUrl = linkedToName;
5702    }
5703
5704    return '<img class="valignmid" src="/images/' + htmlSpecialCharsJS(linkedToIcon) + '" ' +
5705        'width="16" height="16" alt="' + linkedToName + '" /> ' +
5706        '<span class="valignmid">' + linkedToUrl + '</span>';
5707}
5708
5709/**
5710 * Gets google map anchor tag
5711 * @param address
5712 * @param text
5713 * @returns {jQuery|HTMLElement}
5714 */
5715function getGoogleMapsAnchor(address, address2, text)
5716{
5717    return $(
5718        '<a target="_blank" style="text-decoration: none;" href="http://maps.google.com/maps?q=' + encodeURIComponent(address + ' ' + address2) + '" title="Search on Google Maps">' +
5719            '<span class="valignmid">' + text + '</span> <img class="valignmid g-maps" src="/images/icons/google_maps.png">' +
5720        '</a>'
5721    );
5722}
5723
5724/**
5725 * Browser detection by features
5726 * @see https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
5727 *
5728 * @type {{isOpera: boolean, isFirefox: boolean, isSafari: boolean, isIE: boolean, isEdge: boolean, isChrome: boolean}}
5729 */
5730var isIEDetectByFeature = /*@cc_on!@*/false || !!document.documentMode;
5731var browserDetectByFeature = {
5732    // Opera 8.0+
5733    isOpera: (!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0,
5734    // Firefox 1.0+
5735    isFirefox: typeof InstallTrigger !== 'undefined',
5736    // Safari 3.0+ "[object HTMLElementConstructor]"
5737    isSafari: /constructor/i.test(window.HTMLElement) || (function (p) { return p.toString() === "[object SafariRemoteNotification]"; })(!window['safari'] || (typeof safari !== 'undefined' && safari.pushNotification)),
5738    // Internet Explorer 6-11
5739    isIE: isIEDetectByFeature,
5740    // Edge 20+
5741    isEdge: ! isIEDetectByFeature && !!window.StyleMedia,
5742    // Chrome 1 - 73
5743    isChrome: !!window.chrome && (!!window.chrome.webstore || !!window.chrome.runtime || !!window.chrome.loadTimes)
5744};
5745
5746/**
5747 * Inits tooltip
5748 *
5749 * @param $element
5750 */
5751function initToolTipOn($element) {
5752    $element.tooltip(
5753        {
5754            position: {
5755                my: "left bottom-5",
5756                collision: "flip"
5757            },
5758            show: {duration: 100},
5759            hide: {duration: 0},
5760            track: true
5761        }
5762    );
5763}
5764
5765/**
5766 * Gets url vars
5767 */
5768function getUrlVars() {
5769    var vars = {};
5770    var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&#]*)/gi, function (m, key, value) {
5771        vars[key] = value;
5772    });
5773    return vars;
5774}
5775
5776/**
5777 * Detects a mobile device
5778 */
5779function isMobile() {
5780    return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
5781}
5782
5783/**
5784 * DataTable checkbox status keeper
5785 */
5786function DataTableCheckboxStatus(name)
5787{
5788    this.name = name;
5789    this.storage = {};
5790}
5791DataTableCheckboxStatus.prototype.count = function()
5792{
5793    return Object.keys(this.getStorage()).length;
5794}
5795DataTableCheckboxStatus.prototype.isChecked = function(key)
5796{
5797    key = String(key);
5798
5799    return this.getStorage()[key];
5800}
5801DataTableCheckboxStatus.prototype.store = function(key, state)
5802{
5803    if (typeof key === 'undefined' || key === null) {
5804        return;
5805    }
5806
5807    key = String(key);
5808    if (state) {
5809        this.getStorage()[key] = state;
5810    }
5811    else {
5812        delete this.getStorage()[key];
5813    }
5814}
5815DataTableCheckboxStatus.prototype.get = function(key)
5816{
5817    key = String(key);
5818
5819    return this.getStorage()[key] || null;
5820}
5821DataTableCheckboxStatus.prototype.getAll = function() {
5822    return Object.keys(this.getStorage());
5823}
5824DataTableCheckboxStatus.prototype.clear = function()
5825{
5826    this.storage = {};
5827}
5828DataTableCheckboxStatus.prototype.getStorage = function()
5829{
5830    return this.storage;
5831}
5832
5833/**
5834 * DataTable extend default excelHtml5 button. Custom dollar sign handling
5835 */
5836$(document).ready(function () {
5837    if (typeof $.fn.dataTable !== 'undefined') {
5838        $.fn.dataTable.ext.buttons.excelHtml5 = $.extend($.fn.dataTable.ext.buttons.excelHtml5, {
5839            customizeData: function (data) {
5840                let midIndex = data.header.indexOf('MID');
5841                const reMid  = /^(\d{1,})$/i;
5842                const re     = /\((\$[,\d]+(\.\d*)+)\)/i;
5843                let found;
5844                for (var i = 0; i < data.body.length; i++) {
5845                    for (var j = 0; j < data.body[i].length; j++) {
5846                        if (found = data.body[i][j].match(re)) {
5847                            data.body[i][j] = '-' + found[1];
5848                        }
5849                        if (j === midIndex && data.body[i][j].match(reMid)) {
5850                            data.body[i][j] = "`" + data.body[i][j];
5851                        }
5852                    }
5853                }
5854            },
5855            customize: function (xlsx) {
5856                const sheet = xlsx.xl.worksheets['sheet1.xml'];
5857                const reMid = /^`(\d{1,})$/i;
5858                $('c t', sheet).each(function () {
5859                    if ($(this).text().match(reMid)) {
5860                        $(this).text($(this).text().substring(1));
5861                    }
5862                });
5863                $(xlsx.xl["styles.xml"]).find('numFmt[numFmtId="164"]').attr('formatCode', '[$$-45C] #,##0.00_-');
5864            }
5865        });
5866    }
5867});
5868
5869/**
5870 * If there's a previous page in window.history stack go back to previous page. Otherwise go to home page.
5871 */
5872function goToPrevOrHomePage(event) {
5873    event.preventDefault();
5874    window.history.length == 1 ? window.location = '/' : window.history.go(-1);
5875}
5876
5877/**
5878 * Copy text to clipboard.
5879 *
5880 * @param {string} text
5881 */
5882function copyTextToClipboard (text) {
5883    /**
5884     * @param {string} text
5885     */
5886    function fallbackCopyTextToClipboard (text) {
5887        var successful = false,
5888            textArea   = document.createElement("textarea");
5889        textArea.value = text;
5890
5891        // Avoid scrolling to bottom
5892        textArea.style.top      = "0";
5893        textArea.style.left     = "0";
5894        textArea.style.position = "fixed";
5895
5896        document.body.appendChild(textArea);
5897        textArea.focus();
5898        textArea.select();
5899
5900        try {
5901            successful = document.execCommand('copy');
5902            var msg        = successful ? 'successful' : 'unsuccessful';
5903            console.log('Fallback: Copying text command was ' + msg);
5904        } catch (err) {
5905            console.error('Fallback: Oops, unable to copy', err);
5906            successful = false;
5907        }
5908
5909        document.body.removeChild(textArea);
5910        return successful;
5911    }
5912
5913    if (! navigator.clipboard) {
5914        return fallbackCopyTextToClipboard(text);
5915    }
5916    navigator.clipboard.writeText(text).then(function () {
5917        console.log('Async: Copying to clipboard was successful!');
5918    }, function (err) {
5919        console.error('Async: Could not copy text: ', err);
5920        return false;
5921    });
5922    return true;
5923}
5924
5925/**
5926 * Count decimals for number
5927 *
5928 * @return {number} Decimals
5929 */
5930Number.prototype.countDecimals = function () {
5931    if (Math.floor(this.valueOf()) === this.valueOf()) {
5932        return 0;
5933    }
5934
5935    return this.toString().split(".")[1].length || 0;
5936}
5937
5938/**
5939 * Updates tooltip css top/left values for agents tooltips  widely used on the site
5940 *
5941 * @param {object} tooltip    Tooltip DOM object
5942 * @param {object} event      Browser event
5943 * @param {string} [position] Tooltip default positioning
5944 */
5945function setTooltipCoords(tooltip, event, position) {
5946    position = position || 'top right';
5947
5948    var $tooltip     = $(tooltip);
5949    var width        = $tooltip.width();
5950    var height       = $tooltip.height();
5951    var scrollTop    = $(window).scrollTop();
5952    var scrollLeft   = $(window).scrollLeft();
5953    var windowWidth  = $(window).width();
5954    var windowHeight = $(window).height();
5955    var totalWidth   = scrollLeft + windowWidth;
5956    var totalHeight  = scrollTop + windowHeight;
5957
5958    var x        = event.pageX;
5959    var y        = event.pageY;
5960    var tooltipX = x;
5961    var tooltipY = y;
5962
5963    if (x - scrollLeft >= width + 5) {
5964        tooltipX = x - width - 5;
5965    } else if (x + width > totalWidth) {
5966        tooltipX = scrollLeft;
5967    }
5968
5969    if (y - scrollTop >= height + 5) {
5970        tooltipY = y - height - 5;
5971    } else if (y + height > totalHeight) {
5972        tooltipY = scrollTop;
5973    }
5974
5975    // Override default position if possible
5976    if (position.indexOf('right') !== -1 && x + width + 5 < totalWidth) {
5977        tooltipX = x + 5;
5978    }
5979    if (position.indexOf('bottom') !== -1 && y + height + 5 < totalHeight) {
5980        tooltipY = y + 5;
5981    }
5982
5983    $tooltip.css('left', tooltipX < 0 ? scrollLeft : tooltipX);
5984    $tooltip.css('top', tooltipY < 0 ? scrollTop : tooltipY);
5985}
5986
5987/**
5988 * Get Hash From url
5989 *
5990 * @param url
5991 *
5992 * @return {string}
5993 */
5994function getHashFrom(url) {
5995    var hash = url && url.match(/#[\w+].*/gi);
5996    return (hash && hash[0]) || ''
5997}
5998
5999/**
6000 *
6001 * Get text Color By Hexa Color code
6002 *
6003 * @param rgbColor Hexa Color code
6004 *
6005 * @return string
6006 * */
6007function getCategoryStateTextColorBy(rgbColor) {
6008    if (! rgbColor) {
6009        return '#ffffff';
6010    }
6011
6012    var [r, g, b] = rgbColor.match(/[0-9]+/gi)
6013
6014    if ((r*0.299 + g*0.587 + b*0.114) > 130) {
6015        return '#000000';
6016    }
6017
6018    return '#ffffff';
6019}
6020
6021/**
6022 * Snooze domain checker banner and warnings
6023 */
6024function snoozeDomainCheckerBanner($el, type) {
6025    confirmDialog('Would you like to hide this message for 2 weeks?', 'Email Domain Checker', {
6026        width: 'auto',
6027        minHeight: 0
6028    })
6029        .done(function (ok) {
6030            if (ok) {
6031                $.ajax({
6032                    type: 'POST',
6033                    url: '/manage/email/domain-check/snooze',
6034                    success: function () {
6035                        if (type === 'message') {
6036                            const $dialog = $('#dialog-email');
6037
6038                            let messageHeight = $el.outerHeight(true);
6039                            let dialogHeight  = $dialog.outerHeight();
6040
6041                            $dialog.css({'height': (dialogHeight - messageHeight) + 'px'});
6042                        }
6043
6044                        $el.fadeOut();
6045                    }
6046                });
6047            }
6048        });
6049}
6050
6051/**
6052 * Debounce JS function call
6053 *
6054 * @param {Function}
6054 func Function
6055 * @param {number} wait   Waiting for time
6056 *
6057 * @return {(function(...[*]): void)|*}
6058 */
6059const debounce = (func, wait) => {
6060    let timeout;
6061
6062    return function executedFunction (...args) {
6063        const later = () => {
6064            clearTimeout(timeout);
6065            func.apply(this, args);
6066        };
6067
6068        clearTimeout(timeout);
6069        timeout = setTimeout(later, wait);
6070    };
6071}
6072
6073/**
6074 * Serialize Form JSON
6075 *
6076 * @return {object}
6077 */
6078$.fn.serializeToJSON = function () {
6079    const formObjectSerialized = {};
6080    const formArraySerialized  = this.serializeArray();
6081
6082    for(const field of formArraySerialized) {
6083        if (formObjectSerialized[field.name]) {
6084            if (! formObjectSerialized[field.name].push) {
6085                formObjectSerialized[field.name] = [formObjectSerialized[field.name]];
6086            }
6087            formObjectSerialized[field.name].push(field.value || '');
6088        } else {
6089            formObjectSerialized[field.name] = field.value || '';
6090        }
6091    }
6092
6093    return formObjectSerialized;
6094};
6095
6096/**
6097 * Validate Amount Number
6098 *
6099 * @param {string} amount Amount
6100 * @param {number} decimals Number of decimals
6101 *
6102 * @return {string}
6103 */
6104function validateAmountNumber(amount, decimals = 2) {
6105    if ( ! amount) {
6106        return '';
6107    }
6108
6109    let splitDot   = amount.toString().split(".");
6110    let decimal    = false;
6111    const original = parseInt(splitDot[0]);
6112
6113    if (splitDot && splitDot.length > 1) {
6114        const regExp = new RegExp("^\\d{1," + decimals + "}","gi");
6115        splitDot = splitDot[1].match(regExp);
6116
6117        if (splitDot && splitDot.length) {
6118            decimal = splitDot[0];
6119        } else {
6120            decimal = '';
6121        }
6122    }
6123
6124    let num = original.toString().replace(/\B(?=(?:\d{3})+(?!\d))/g, ",");
6125    if (decimal !== false) {
6126        num += '.' + decimal;
6127    }
6128
6129    return num;
6130}
6131
6132/**
6133 * Check Is a JSON string
6134 *
6135 * @param {string} string
6136 * @return {boolean}
6137 */
6138function checkIsJSON(string) {
6139    try {
6140        if (Number(string) === 0) {
6141            throw new Error('JSON string not valid.');
6142        }
6143        JSON.parse(string);
6144    } catch (e) {
6145        return false;
6146    }
6147    return true;
6148}
6149
6150$(function () {
6151    if ($.ui.version !== '1.13.0') {
6152        return;
6153    }
6154    $.ui.tooltip.prototype._registerCloseHandlers = function(event, target) {
6155        var events = {
6156            keyup: function( event ) {
6157                if ( event.keyCode === $.ui.keyCode.ESCAPE ) {
6158                    var fakeEvent = $.Event( event );
6159                    fakeEvent.currentTarget = target[ 0 ];
6160                    this.close( fakeEvent, true );
6161                }
6162            }
6163        };
6164        // Only bind remove handler for delegated targets. Non-delegated tooltips will handle this in destroy.
6165        if ( target[ 0 ] !== this.element[ 0 ] ) {
6166            events.remove = function() {
6167                var targetElement = this._find( target );
6168                if ( targetElement ) {
6169                    this._removeTooltip( targetElement.tooltip );
6170                }
6171            };
6172        }
6173
6174        if ( !event || event.type === "mouseover" ) {
6175            events.mouseleave = "close";
6176        }
6177        if ( !event || event.type === "focusin" ) {
6178            events.focusout = "close";
6179        }
6180        this._on( true, target, events );
6181    };
6182});
6183
6184/**
6185 * Sends mass SMS
6186 *
6187 * @param {array} leadIds
6188 * @param {array} statusIds
6189 * @param {array} savedFilters
6190 */
6191function sendMassSms(leadIds, statusIds, savedFilters) {
6192    if (window.smsMassWindow) return;
6193
6194    window.smsMassWindow = $('<div />').appendTo($('body'));
6195    window.smsMassWindow.load('/dialerMessage/showFormMass', {
6196        leadIds: leadIds,
6197        statusIds: statusIds,
6198        savedFilters: savedFilters,
6199    });
6200}
6201
6202$(document).on('click', '.js-dialer-sms', function () {
6203    if (window.smsWindow) return;
6204
6205    const $icon = $(this);
6206    const $input = $icon.parent().find('input');
6207    const phoneNumber = getClosestPhoneNumber($input.get(0), $icon.get(0));
6208    if (!phoneNumber) {
6209        return false;
6210    }
6211
6212    window.smsWindow = $('<div />').appendTo($('body'));
6213    window.smsWindow.smsTarget = $input.length > 0 ? $input : $icon;
6214    window.smsWindow.load('/dialerMessage/showForm', {
6215        phoneNumber: phoneNumber,
6216        leadId: $icon.data('lead_id'),
6217        contact: $icon.data('contact_name'),
6218        contactFieldId: $icon.data('contact_field_id')
6219    });
6220});
6221
6222/**
6223 * Prompt Create Lead Ticket On Status Change
6224 *
6225 * @param {{ leadId, statusId, ticketTypeName, skipStatusChange, leadStatusName }} ticketTemplate
6226 */
6227function promptCreateLeadTicketOnStatusChange(ticketTemplate) {
6228    $('#request-prompt-create-ticket').remove();
6229
6230    if (! ticketTemplate.ticketTypeName) {
6231        alertDialog(`
6232            <p>The Ticket Type associated with this lead status change no longer exists.</p>
6233            <br />
6234            <p>Please contact an administrator to update the settings for: <b>${htmlSpecialCharsJS(ticketTemplate.leadStatusName)}</b>.</p>
6235        `, 'Cannot Create Helpdesk Ticket', { width: 520 });
6236        return;
6237    }
6238
6239    let message = `Would you like to create a ${htmlSpecialCharsJS(ticketTemplate.ticketTypeName)} ticket in the Helpdesk?`;
6240    if (ticketTemplate.skipStatusChange) {
6241        message = `
6242            <p>To use this lead status, a ${htmlSpecialCharsJS(ticketTemplate.ticketTypeName)} ticket must be created in the Helpdesk.</p>
6243            <br />
6244            <p>Do you want to proceed?</p>`;
6245    }
6246
6247    const deferred = $.Deferred();
6248    deferred.promise();
6249
6250    $(`<div id="request-prompt-create-ticket">
6251                    ${message}
6252                </div>`).dialog({
6253        title: 'Create Helpdesk Ticket?',
6254        width: 480,
6255        buttons:{
6256            cancel: {
6257                text: 'No',
6258                click: function() {
6259                    deferred.reject();
6260                    $(this).dialog('close');
6261                }
6262            },
6263            save: {
6264                text: 'Yes',
6265                click: function () {
6266                    $(this).dialog('close');
6267                    deferred.resolve();
6268                    window.open(`/helpdesk?action=addnew&lid=${ticketTemplate.leadI
6268d}&statusId=${ticketTemplate.statusId}`, '_blank');
6269                }
6270            }
6271        }
6272    });
6273
6274    return deferred;
6275}
6276
6277/**
6278 * Activate the select2 plugin inside dialog, you need to call this function at the top of the `open` prop method
6279 *
6280 * https://stackoverflow.com/a/19788419
6281 */
6282function activeSelect2InsideDialog(_this){
6283    if ($.ui && $.ui.dialog && !$.ui.dialog.prototype._allowInteractionRemapped && $(_this).closest(".ui-dialog").length) {
6284        if ($.ui.dialog.prototype._allowInteraction) {
6285            $.ui.dialog.prototype._allowInteraction = (e) => {
6286                return true;
6287            };
6288            $.ui.dialog.prototype._allowInteractionRemapped = true;
6289        }
6290        else {
6291            $.error("You must upgrade jQuery UI or else.");
6292        }
6293    }
6294}
6295
6296/**
6297 * The Search-Field element will be rendered at the end of the document
6298 *
6299 * @link https://forums.select2.org/t/search-being-unfocused/1203/11
6300 */
6301function autofocusSearchFieldInSelect2 () {
6302    $(document).on('select2:open', () => {
6303        // If we click on select2 and immediately click on another select2
6304        // the querySelector will refer to the previous element
6305        // for this reason we have to add our function to the queue via setTimeout
6306        setTimeout(() => {
6307            document.querySelector('.select2-search__field').focus();
6308        }, 0)
6309    });
6310}
6311
6312function numberInputField(element, decimals) {
6313    decimals = decimals || 2;
6314    const numberInput = $(element);
6315    numberInput.off('keypress paste blur');
6316    numberInput
6317        .on('keypress', function (e) {
6318            var text  = String.fromCharCode(! e.charCode ? e.which : e.charCode);
6319            var value = numberInput.val();
6320            if (! text.match(/[\-0-9\.]/)) {
6321                return false;
6322            } else if (value.indexOf('.') !== -1 && text == '.') {
6323                return false;
6324            } else if (value.indexOf('-') !== -1 && text == '-') {
6325                return false;
6326            }
6327        })
6328        .on('paste blur',function () {
6329            setTimeout(function() {
6330                var data = numberInput.val() ;
6331                data = data.replace(/[^\-0-9.,]/gi, '');
6332                data = data.replace(/,+/gi, '');
6333                data = data.replace(/\.+/gi, '.');
6334                if (data == '') {
6335                    numberInput.val('');
6336                    return;
6337                }
6338                data = parseFloat(data);
6339                if (isNaN(data)) {
6340                    data = 0.0;
6341                }
6342                numberInput.val(parseFloat(data.toFixed(decimals)));
6343            });
6344        });
6345}

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.