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 '&': '&', 1673 '<': '<', 1674 '>': '>', 1675 '"': '"', 1676 "'": ''' 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 Ӓ 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, " "); 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.