$(function () { //Configure cors to fix issues with widget communication $.support.cors = true; /* * Set passive event handlers for touchstart and touchmove events since our version of jQuery doesn't automatically * handle this. Browsers are now expecting these events to be set as passive if no other explicit handler exists. * The code below prevents hundreds of verbose logging errors and slightly improves load times */ jQuery.event.special.touchstart = { setup: function( _, ns, handle ) { if (ns.indexOf('noPreventDefault') === -1) { this.addEventListener('touchstart', function (e) { if (e.cancelable) { e.preventDefault() } }, { passive: true }); } else { this.addEventListener('touchstart', handle, { passive: false }); } } }; jQuery.event.special.touchmove = { setup: function( _, ns, handle ) { if (ns.indexOf('noPreventDefault') === -1) { this.addEventListener('touchmove', function (e) { if (e.cancelable) { e.preventDefault() } }, { passive: true }); } else { this.addEventListener('touchmove', handle, { passive: false }); } } }; //disable links which are marked with disabled class $('body').on('click', 'a.disabled', function (event) { rcra.notifications.showWarnMessage("We are sorry but this feature is currently under construction."); event.preventDefault(); }); $('li.disabled > a').not('li.paginate_button > a').on('click', null, function (event) { rcra.notifications.showWarnMessage("We are sorry but this feature is currently under construction."); event.preventDefault(); }); $(document).on('shown.bs.modal', '.modal', function () { var zIndex = 1040 + (10 * $('.modal:visible').length); $(this).css('z-index', zIndex); $('.modal-backdrop').not('.modal-stack').css('z-index', zIndex - 1).addClass('modal-stack'); var elements = $(this).find('input:first, select:first, textarea:first'); if (elements && elements.length > 0) { if ($(elements[0]).data("autoFocus") === undefined || $(elements[0]).data("autoFocus")){ elements[0].focus(); } } }); $(document).on('hidden.bs.modal', '.modal', function () { //Removes any orphaned bootstrap modal overlays created by knockstrap in order to prevent UI blocking issues once modal is closed if (!$('.modal:visible')) { $('.modal-backdrop').remove(); } }); $.extend(true, $.fn.dataTable.defaults, { "stateSave": true }); //Fix all responsive issues on Datatable on initialization $(document).on( 'init.dt', function ( e, settings ) { var api = new $.fn.dataTable.Api( settings ); rcra.utils.fixDataTable(api.table().node()); }); //Modify knockstrap modal templating, formatting, and styling ko.stringTemplateEngine.prototype.addTemplate("modalHeader", '\n' + '
\n' + ''); //Validation ko.validation.rules.pattern.message = 'Invalid.'; /** * Validates the precision of an entered number. the value should be provided as: * [wholePrecision, decimalPrecision] * * Example: * ko.observable().extend({precision: [3,2]}); * valid values: * 0 * 333 * .22 * 333.22 * null * undefined * * invalid values: * 4444.22 * 333.333 * * @type {{validator: ko.validation.rules.precision.validator, message: string}} */ ko.validation.rules['precision'] = { validator: function(val, params) { var whole = params[0]; if(whole == null || whole == undefined || isNaN(whole) || whole == 0) { throw "The value you provided for your whole number precision (" + whole + ") is not a valid number or is 0."; } var decimal = params[1]; if(decimal == null || decimal == undefined || isNaN(decimal)) { throw "The value you provided for your decimal number precision (" + decimal + ") is not a valid number."; } var regex = decimal ? new RegExp('^-?\\d{0,' + whole + '}(\\.\\d{1,' + decimal + '})?$') : new RegExp('^\\d{0,' + whole + '}$'); var result = regex.test(val) || val == null || val == undefined; return result; }, message: "Value must be a number and contain no more than {0} whole digit(s) and {1} decimal digit(s)" } /* * https://github.com/Knockout-Contrib/Knockout-Validation/wiki/User-Contributed-Rules * This rules checks the given array of objects/observables and returns * true if at least one of the elements validates against the default * 'required' rules * * Example: * * * self.mobilePhone.extend({ requiresOneOf: [self.homePhone, self.mobilePhone] }); * self.homePhone.extend({ requiresOneOf: [self.homePhone, self.mobilePhone] }); * */ ko.validation.rules['requiresOneOf'] = { getValue: function (o) { return (typeof o === 'function' ? o() : o); }, validator: function (val, fields) { var self = this; var anyOne = ko.utils.arrayFirst(fields, function (field) { var stringTrimRegEx = /^\s+|\s+$/g, testVal; var val = self.getValue(field); if (val === undefined || val === null) return false; testVal = val; if (typeof (val) == "string") { testVal = val.replace(stringTrimRegEx, ''); } return ((testVal + '').length > 0); }); return (anyOne != null); }, message: 'One of these fields is required' }; /** * Validates the validity of passed in handlerId. the State lookup should be provided with the rules as: * stateLookup * * Example: * ko.observable().extend({ validHandlerId: states }); * * * @type {{validator: ko.validation.rules.validHandlerId.validator, message: string}} */ self.handlerIdCheck = function (handlerId, states) { var activityLocation = handlerId.slice(0, 2); if (activityLocation.length == 2) { activityLocation = activityLocation.toUpperCase(); if (activityLocation == 'FC') return activityLocation; return ko.utils.arrayFirst(states, function (state) { return ko.unwrap(state.code) == activityLocation; }); } return null; }; var validHandlerIdMessages = { minLength: "Please enter at least 3 characters.", maxLength: "Please enter no more than 12 characters.", invalidId: "The Handler Id must start with a valid Activity Location.", alphaNumeric: "The Handler Id must only contain alphanumeric characters." }; ko.validation.rules['validHandlerId'] = { getValue: function (o) { return (typeof o === 'function' ? o() : o); }, validator: function (val, states) { if (!val) return true; if (! ko.validation.rules.minLength.validator(val, 3)) { ko.validation.rules.validHandlerId.message = validHandlerIdMessages.minLength; return false; } if(! ko.validation.rules.maxLength.validator(val, 12)){ ko.validation.rules.validHandlerId.message = validHandlerIdMessages.maxLength; return false; } if (! ko.validation.rules.alphaNumericOnly.validator(val)){ ko.validation.rules.validHandlerId.message = validHandlerIdMessages.alphaNumeric; return false; } // Set correct message after we pass all other validation ko.validation.rules.validHandlerId.message = validHandlerIdMessages.invalidId return self.handlerIdCheck(val, states); }, message: validHandlerIdMessages.invalidId }; ko.validation.rules['lessThanDateCheck'] = { getValue: function (o) { return (typeof o === 'function' ? o() : o); }, validator: function (val, date) { if (!val || !date) return true; return val <= date; }, message: function(date) { return 'Field must be less than or equal to ' + moment (date).format ('L'); }, }; ko.validation.rules['greaterThanDateCheck'] = { getValue: function (o) { return (typeof o === 'function' ? o() : o); }, validator: function (val, date) { if (!val || !date) return true; return val >= date; }, message: function(date) { return 'Field must be greater than or equal to ' + moment (date).format ('L'); }, }; ko.validation.rules['betweenValues'] = { validator: function (val, params) { var numericValue = ko.unwrap(params.value); if (!numericValue){ if (!val) return true; numericValue = ko.unwrap(val); } var lower = params.lower, upper = params.upper; if (!lower || !upper) lower = 0, upper = 0; return lower <= Number(numericValue) && upper >= Number(numericValue); }, message: function(params) { return 'Field must be between {0} and {1}'; }, }; //Clear out default rules so we can set optional name property to identify rule for (var ruleName in ko.validation.rules) { if (ko.validation.rules.hasOwnProperty(ruleName) && ko.extenders.hasOwnProperty(ruleName)) { delete ko.extenders[ruleName]; } } //Add name property to all rules ko.validation.addExtender = function (ruleName) { ko.extenders[ruleName] = function (observable, params) { if (params && (params.message || params.onlyIf)) { return ko.validation.addRule(observable, { rule: ruleName, message: params.message, params: ko.validation.utils.isEmptyVal(params.params) ? true : params.params, condition: params.onlyIf, name: params.name, type: params.type || 'error' }); } else { return ko.validation.addRule(observable, { rule: ruleName, params: params }); } }; }; ko.validation.init({ registerExtenders: true, messagesOnModified: true, insertMessages: true, parseInputAttributes: true, messageTemplate: null, decorateInputElement: true, writeInputAttributes: true, errorElementClass: 'error', warningElementClass: 'warning' }, true); //Modifies validation grouping to focus on first error element var validationGrouping = ko.validation.group; ko.validation.group = function group(obj, options) { var result = validationGrouping(obj, options); result.ignoreWarnings = function () { var errors = []; result.forEach(function (observable) { if (ko.validation.utils.isValidatable(observable) && !observable.isValid()) { if (rcra.validation.isWarning(observable)) { return; } errors.push(observable.error.peek()); } }); return errors; }; var showAllMessages = result.showAllMessages; result.showAllMessages = function (show) { showAllMessages(show); if(show != false) { var $elem = $(".validationMessage:visible").first(); if ($elem.length > 0 && $(".modal-dialog").is(":visible") == false) { $('html, body').animate({ scrollTop: $elem.offset().top - 175 }, 1000); } else if ($elem.length > 0 && $(".modal-dialog").is(":visible") == true) { $elem.parent().get(0).scrollIntoView({block: 'start', behavior: 'smooth'}) } } }; return result; }; var originalInsertValidationMessage = ko.validation.insertValidationMessage; ko.validation.insertValidationMessage = function (element) { originalInsertValidationMessage(element); var span = element.nextSibling; var context = ko.contextFor(element) var bindings = ko.bindingProvider.instance.getBindings(element, context); if (bindings.value) { ko.applyBindingsToNode(span, {validationElement: bindings.value}, ko.dataFor(element)); } return span; }; //Allow date fields to be validatable ko.validation.makeBindingHandlerValidatable("date"); ko.validation.makeBindingHandlerValidatable("bootstrapSwitchOn"); ko.validation.makeBindingHandlerValidatable("text"); ko.validation.makeBindingHandlerValidatable("handlerId"); ko.validation.makeBindingHandlerValidatable("maskedPhoneExt"); ko.validation.makeBindingHandlerValidatable("lookupValue"); ko.validation.makeBindingHandlerValidatable("fileInput"); //Load custom datatable sort functions jQuery.fn.dataTableExt.oSort["us-date-desc"] = function (a, b) { var x = new Date(a), y = new Date(b); return ((x < y) ? -1 : ((x > y) ? 1 : 0)); }; jQuery.fn.dataTableExt.oSort["us-date-asc"] = function (a, b) { var x = new Date(a), y = new Date(b); return ((x < y) ? 1 : ((x > y) ? -1 : 0)); }; //Temporarily save KO MultiSelect binding handler in order to override var originalMultiselectInit = ko.bindingHandlers.multiselect.init; //Override KO MultiSelect init binding handler ko.bindingHandlers.multiselect.init = function (element, valueAccessor, allBindings, viewModel, bindingContext) { originalMultiselectInit(element, valueAccessor, allBindings, viewModel, bindingContext); var $originalSelect = $(element); var $elem = $(element).next('div').find('button.multiselect.dropdown-toggle'); if (!$originalSelect.attr('multiple')) { var $clearAll = $("") .attr("id", $originalSelect.attr("id") + "_clearAll") .attr("href", "#") .attr("title", "Clear Selection") .css({"display": "none", "z-index": "5000", "position": "absolute", "right": "10px", "top": "35%"}) .addClass("clear-all"); if ($originalSelect.val()) { $clearAll.show(); } $originalSelect.change(function () { $clearAll.toggle(!!$(this).val()); }); var clearValue = function () { //Hack to fix an IE issue where value was not clearing for (var i = 0; i < 2; i++) { setTimeout(function () { var valObsv = allBindings().value; valObsv(undefined); $originalSelect.trigger('change'); }, 50); } }; $clearAll.appendTo($elem.parent("div.btn-group")); $clearAll.click(function (e) { e.preventDefault(); clearValue(); }); //Hack to fix an IE issue where the first option is selected when no value exists if (!allBindings().value()) { clearValue(); } } $elem.click(function () { //Focus on the search filter input element var $searchInput = $(element).next('div').find('ul.multiselect-container').find('.multiselect-search'); var $clearButton = $(element).next('div').find('ul.multiselect-container').find('.multiselect-clear-filter'); var $searchButton = $(element).next('div').find('ul.multiselect-container').find('.glyphicon-search').parent(); $searchButton.attr("title", "Enter search criteria"); $clearButton.attr("title", "Clear search criteria"); var $listItems = $(element).next('div').find('ul.multiselect-container').find('li').not(".multiselect-item.multiselect-filter"); $searchInput.on('keydown', function (e) { var keyCode = e.keyCode || e.which; if (keyCode == 13) { e.preventDefault(); if ($listItems.length > 0) { setTimeout(function () { $($listItems).not('.multiselect-filter-hidden').first().find('a').focus(); }, 100); } } }); $listItems.on('keydown', function (e) { var keyCode = e.keyCode || e.which; if (keyCode == 27) { e.preventDefault(); $searchInput.focus(); } }); setTimeout(function () { $searchInput.focus(); }, 100); }); $(element).attr('tabindex', '-1'); //Overrides third-party multiselect functionality (lines 641-648) when selecting a range of options by holding the select key //Fixes an issue that included hidden options in the range selection when the dropdown is filtered via text search setTimeout(function () { var multiselectInstance = $(element).data('multiselect'); if (multiselectInstance) { $('li a', multiselectInstance.$ul).unbind('click'); $('li a', multiselectInstance.$ul).unbind('touchstart'); $('li a', multiselectInstance.$ul).on('touchstart click', $.proxy(function (event) { event.stopPropagation(); var $target = $(event.target); if (event.shiftKey && multiselectInstance.options.multiple) { if ($target.is("label")) { event.preventDefault(); $target = $target.find("input"); $target.prop("checked", !$target.prop("checked")); } var checked = $target.prop('checked') || false; if (multiselectInstance.lastToggledInput !== null && multiselectInstance.lastToggledInput !== $target) { // Make sure we actually have a range var from = $target.closest("li").index(); var to = multiselectInstance.lastToggledInput.closest("li").index(); if (from > to) { // Swap the indices var tmp = to; to = from; from = tmp; } // Make sure we grab all elements since slice excludes the last index ++to; // Change the checkboxes and underlying options var range = multiselectInstance.$ul.find("li").slice(from, to).find("input"); range.each(function (index) { var isVisible = $(this).is(":visible"); if (isVisible) { $(this).prop('checked', checked); if (multiselectInstance.options.selectedClass) { $(this).closest('li') .toggleClass(multiselectInstance.options.selectedClass, checked); } var $option = multiselectInstance.getOptionByValue($(this).val()); $option.prop('selected', checked); } }); } // Trigger the select "change" event $target.trigger("change"); } // Remembers last clicked option if ($target.is("input") && !$target.closest("li").is(".multiselect-item")) { multiselectInstance.lastToggledInput = $target; } $target.blur(); }, multiselectInstance)); } }, 100); }; //Temporarily save KO MultiSelect binding handler in order to override var originalMultiselectUpdate = ko.bindingHandlers.multiselect.update; ko.bindingHandlers.multiselect.update = function (element, valueAccessor, allBindings, viewModel, bindingContext) { var selectionList = ko.unwrap (allBindings().options); var multiSelect = ko.unwrap (allBindings().multiselect); var ele = $(element); var buttonTextFn = multiSelect.buttonText ? multiSelect.buttonText : function (options, selected){ if (this.disabledText.length > 0 && ((ele.prop('disabled') && selectionList.length == 0) || (ele.children().length == 0 && this.disableIfEmpty))) { return this.disabledText; } else if (options.length === 0) { return this.nonSelectedText; } else if (this.allSelectedText && options.length === $('option', ele).length && $('option', ele).length !== 1 && this.multiple) { if (this.selectAllNumber) { return this.allSelectedText + ' (' + options.length + ')'; } else { return this.allSelectedText; } } else if (options.length > this.numberDisplayed) { return options.length + ' ' + this.nSelectedText; } else { var selected = ''; var delimiter = this.delimiterText; options.each(function() { var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).text(); selected += label + delimiter; }); return selected.substr(0, selected.length - this.delimiterText.length); } }; ele.multiselect('setOptions', { buttonText: buttonTextFn }); ele.multiselect('rebuild'); }; /* * Override options binding handler update event in order to properly support valueAllowUnset on dropdowns * The only changes made to the binding handler is the addition of 'lookup' and 'lookupValue' when checking * current value of valueAllowUnset binding since those are custom rcra bindings. */ var optionsCaptionPlaceholder = {}; ko.bindingHandlers.options.update = function (element, valueAccessor, allBindings) { function selectedOptions() { return ko.utils.arrayFilter(element.options, function (node) { return node.selected; }); } var selectWasPreviouslyEmpty = element.length == 0, multiple = element.multiple, previousScrollTop = (!selectWasPreviouslyEmpty && multiple) ? element.scrollTop : null, unwrappedArray = ko.utils.unwrapObservable(valueAccessor()), valueAllowUnset = allBindings.get('valueAllowUnset') && (allBindings['has']('value') || allBindings['has']('lookup')), includeDestroyed = allBindings.get('optionsIncludeDestroyed'), arrayToDomNodeChildrenOptions = {}, captionValue, filteredArray, previousSelectedValues = []; if (!valueAllowUnset) { if (multiple) { previousSelectedValues = ko.utils.arrayMap(selectedOptions(), ko.selectExtensions.readValue); } else if (element.selectedIndex >= 0) { previousSelectedValues.push(ko.selectExtensions.readValue(element.options[element.selectedIndex])); } } if (unwrappedArray) { if (typeof unwrappedArray.length == "undefined") // Coerce single value into array unwrappedArray = [unwrappedArray]; // Filter out any entries marked as destroyed filteredArray = ko.utils.arrayFilter(unwrappedArray, function(item) { return includeDestroyed || item === undefined || item === null || !ko.utils.unwrapObservable(item['_destroy']); }); // If caption is included, add it to the array if (allBindings['has']('optionsCaption')) { captionValue = ko.utils.unwrapObservable(allBindings.get('optionsCaption')); // If caption value is null or undefined, don't show a caption if (captionValue !== null && captionValue !== undefined) { filteredArray.unshift(optionsCaptionPlaceholder); } } } else { // If a falsy value is provided (e.g. null), we'll simply empty the select element } function applyToObject(object, predicate, defaultValue) { var predicateType = typeof predicate; if (predicateType == "function") // Given a function; run it against the data value return predicate(object); else if (predicateType == "string") // Given a string; treat it as a property name on the data value return object[predicate]; else // Given no optionsText arg; use the data value itself return defaultValue; } // The following functions can run at two different times: // The first is when the whole array is being updated directly from this binding handler. // The second is when an observable value for a specific array entry is updated. // oldOptions will be empty in the first case, but will be filled with the previously generated option in the second. var itemUpdate = false; function optionForArrayItem(arrayEntry, index, oldOptions) { if (oldOptions.length) { previousSelectedValues = !valueAllowUnset && oldOptions[0].selected ? [ ko.selectExtensions.readValue(oldOptions[0]) ] : []; itemUpdate = true; } var option = element.ownerDocument.createElement("option"); if (arrayEntry === optionsCaptionPlaceholder) { ko.utils.setTextContent(option, allBindings.get('optionsCaption')); ko.selectExtensions.writeValue(option, undefined); } else { // Apply a value to the option element var optionValue = applyToObject(arrayEntry, allBindings.get('optionsValue') || allBindings.get('lookup'), arrayEntry); ko.selectExtensions.writeValue(option, ko.utils.unwrapObservable(optionValue)); // Apply some text to the option element var optionText = applyToObject(arrayEntry, allBindings.get('optionsText'), optionValue); ko.utils.setTextContent(option, optionText); } return [option]; } // By using a beforeRemove callback, we delay the removal until after new items are added. This fixes a selection // problem in IE<=8 and Firefox. See https://github.com/knockout/knockout/issues/1208 arrayToDomNodeChildrenOptions['beforeRemove'] = function (option) { element.removeChild(option); }; function setSelectionCallback(arrayEntry, newOptions) { if (itemUpdate && valueAllowUnset) { // The model value is authoritative, so make sure its value is the one selected // There is no need to use dependencyDetection.ignore since setDomNodeChildrenFromArrayMapping does so already. ko.selectExtensions.writeValue(element, ko.utils.unwrapObservable(allBindings.get('value') || allBindings.get('lookupValue')), true /* allowUnset */); } else if (previousSelectedValues.length) { // IE6 doesn't like us to assign selection to OPTION nodes before they're added to the document. // That's why we first added them without selection. Now it's time to set the selection. var isSelected = ko.utils.arrayIndexOf(previousSelectedValues, ko.selectExtensions.readValue(newOptions[0])) >= 0; var optionNodeSelectionStateFunc = ko.utils.sc || ko.utils.setOptionNodeSelectionState; optionNodeSelectionStateFunc(newOptions[0], isSelected); // If this option was changed from being selected during a single-item update, notify the change if (itemUpdate && !isSelected) { ko.ignoreDependencies(ko.utils.triggerEvent, null, [element, "change"]); } } } var callback = setSelectionCallback; if (allBindings['has']('optionsAfterRender') && typeof allBindings.get('optionsAfterRender') == "function") { callback = function(arrayEntry, newOptions) { setSelectionCallback(arrayEntry, newOptions); ko.ignoreDependencies(allBindings.get('optionsAfterRender'), null, [newOptions[0], arrayEntry !== optionsCaptionPlaceholder ? arrayEntry : undefined]); } } ko.utils.setDomNodeChildrenFromArrayMapping(element, filteredArray, optionForArrayItem, arrayToDomNodeChildrenOptions, callback); ko.ignoreDependencies(function () { if (valueAllowUnset) { // The model value is authoritative, so make sure its value is the one selected ko.selectExtensions.writeValue(element, ko.utils.unwrapObservable(allBindings.get('value') || allBindings.get('lookupValue')), true /* allowUnset */); } else { // Determine if the selection has changed as a result of updating the options list var selectionChanged; if (multiple) { // For a multiple-select box, compare the new selection count to the previous one // But if nothing was selected before, the selection can't have changed selectionChanged = previousSelectedValues.length && selectedOptions().length < previousSelectedValues.length; } else { // For a single-select box, compare the current value to the previous value // But if nothing was selected before or nothing is selected now, just look for a change in selection selectionChanged = (previousSelectedValues.length && element.selectedIndex >= 0) ? (ko.selectExtensions.readValue(element.options[element.selectedIndex]) !== previousSelectedValues[0]) : (previousSelectedValues.length || element.selectedIndex >= 0); } // Ensure consistency between model value and selected option. // If the dropdown was changed so that selection is no longer the same, // notify the value or selectedOptions binding. if (selectionChanged) { ko.utils.triggerEvent(element, "change"); } } }); // Workaround for IE bug var renderFunc = ko.utils.Nc || ko.utils.ensureSelectElementIsRenderedCorrectly; renderFunc(element); if (previousScrollTop && Math.abs(previousScrollTop - element.scrollTop) > 20) element.scrollTop = previousScrollTop; }; //Temporarily save KO Validation binding handler in order to override var originalValidationElementUpdate = ko.bindingHandlers.validationElement.update; //Override KO Validation Element binding handler ko.bindingHandlers.validationElement.update = function (element, valueAccessor, allBindingsAccessor) { var obsv = valueAccessor(); var preSelectValue = function () { if (!$(element).is('select') || $(element).data('autoSelect') == false || !ko.bindingProvider['instance'].nodeHasBindings(element)) { return; } setTimeout(function () { //Timeout needed because funky things were happening with validation otherwise //Select the only option in a dropdown when no value exists and the field is required var ctx = ko.contextFor(element); if (!ctx) { //No need to run pre-selection if context doesn't exist return; } var allBindings = ko.bindingProvider['instance'].getBindings(element, ctx); if (!!allBindings.lookupValue && !!allBindings.lookup) { if (ko.unwrap(allBindings.lookup).length == 1 && !ko.unwrap(obsv)) { //Set as the first item in the lookup list obsv(ko.unwrap(allBindings.lookup)[0]); } } else if (!!allBindings.value && !!allBindings.options) { if (ko.unwrap(allBindings.options).length == 1 && !ko.unwrap(obsv)) { var options = ko.unwrap(allBindings.options); var optionsValue = ko.unwrap(allBindings.optionsValue); //Set as the first item in the lookup list if (optionsValue) { obsv(ko.unwrap(options[0][optionsValue])); } else { obsv(options[0]); } } } }, 250); }; if (allBindingsAccessor().validationElement.rules) { $.each(allBindingsAccessor().validationElement.rules(), function (index, validator) { if (validator.rule == 'required') { if (typeof validator.condition != 'undefined') { if (validator.condition() === true) { //Set indicator because required condition resolves as true rcra.validation.setRequiredElementIndicator(element); preSelectValue(); } else { //Remove indicator because required condition resolves as false rcra.validation.removeRequiredElementIndicator(element); } } else { //Set indicator because this field is always required rcra.validation.setRequiredElementIndicator(element); preSelectValue(); } } }); if (!allBindingsAccessor().validationElement.rules.lookupByProp('required', 'rule')) { //required rule may have been removed so remove indicator rcra.validation.removeRequiredElementIndicator(element); } } //Call original binding handler to invoke native KO Validation functionality originalValidationElementUpdate(element, valueAccessor, allBindingsAccessor); var getCssSettingsAccessor = function (type) { return function () { var css = {}; var shouldShow = ((!ko.validation.configuration.decorateElementOnModified || ko.unwrap(obsv.isModified)) ? !ko.unwrap(obsv.isValid) : false); if (type === 'warning') { css[ko.validation.configuration.warningElementClass] = shouldShow; css[ko.validation.configuration.errorElementClass] = false; } else { css[ko.validation.configuration.errorElementClass] = shouldShow; css[ko.validation.configuration.warningElementClass] = false; } return css; } }; ko.bindingHandlers.css.update(element, getCssSettingsAccessor( rcra.validation.isWarning(obsv) ? 'warning' : 'error'), allBindingsAccessor); }; //Setting error CSS on Select2 Dropdown (since it's not supported by KO Validation by default) var originalBindingCssUpdate = ko.bindingHandlers.css.update; ko.bindingHandlers.css.update = function (element, cssSettingsAccessor, allBindingsAccessor) { var isValid = !cssSettingsAccessor().error; if ($(element).hasClass("select2-offscreen")) { var $select2Container = $(element).prev('.select2-container'); if (isValid) { $select2Container.removeClass('error'); } else { $select2Container.addClass('error'); } } if ($(element).is(':hidden')) { var $multiselectContainer = $(element).next('div.btn-group').find('button.multiselect'); if (isValid) { $multiselectContainer.removeClass('error'); } else { $multiselectContainer.addClass('error'); } } originalBindingCssUpdate(element, cssSettingsAccessor, allBindingsAccessor); }; var originalApplyBindings = ko.applyBindings; ko.applyBindings = function (viewModel, rootNode) { originalApplyBindings(viewModel, rootNode); if (rootNode) { $(rootNode).trigger("ko:bound", [viewModel]); if ($(rootNode).attr("displayOnLoad") !== undefined) { $(rootNode).removeAttr("hidden"); } } }; var originalPopoverBindingUpdate = ko.bindingHandlers.popover.update; ko.bindingHandlers.popover.update = function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { originalPopoverBindingUpdate(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext); var popoverData = $(element).data('bs.popover'); if (popoverData) { popoverData.options.delay = {hide: 50}; //Edit template if there are additional classes if (popoverData.options.additionalClasses) { var classes = popoverData.options.additionalClasses; popoverData.options.template = popoverData.options.template.replace("popover", "popover " + classes); } if (popoverData.options.height) { popoverData.options.template = popoverData.options.template.replace('class="popover-content"', 'class="popover-content" style="max-height: ' + popoverData.options.height + '"'); } $(element).on("click", function (e) { e.preventDefault(); }); $(element).on("mouseenter", function () { $(popoverData.$tip).one("mouseenter", function () { popoverData.hoverState = "in"; }).one("mouseleave", function () { popoverData.hoverState = "out"; popoverData.hide(); }); }); } }; // Override Text Binding var originalTextBindingUpdate = ko.bindingHandlers.text.update; ko.bindingHandlers.text.update = function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { if (allBindingsAccessor && allBindingsAccessor().enum){ var enumValue = allBindingsAccessor().enum[ko.unwrap(valueAccessor())]; originalTextBindingUpdate(element, ko.observable(enumValue), allBindingsAccessor, viewModel, bindingContext); } else { originalTextBindingUpdate(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext); } }; //Pager pager.Href.hash = '#!/'; pager.afterShow.add(rcra.utils.fixDataTables); //Ajax Handlers $(document).ajaxComplete(function (event, req, settings) { if (req != undefined && (req.status < 200 || req.status > 299) && req.status != 0) { if (req.status === 401) { rcra.notifications.showWarnMessage("You have been logged out due to inactivity. You will be redirected " + "to login in page momentarily."); setTimeout(function () { window.location.href = req.getResponseHeader("redirect-url"); }, 4000); return; } if (req.status == 500) { var defaultMessage = "We are very sorry, but it looks like we encountered a problem."; try { var response = req.responseJSON; if (response.code != "E_ValidationError") { var errorInfo = ''; if(response && response.errorId) { errorInfo += '[' + response.errorId + '] - '; } if(response && response.errorDate) { errorInfo += moment(response.errorDate).format('MM/DD/YYYY HH:mm:ss SSS Z'); } errorInfo += ''; rcra.notifications.showErrorMessage(defaultMessage + " " + response.message, { title: "Error - " + response.code + errorInfo }); } } catch (err) { rcra.logger.debug("Something went wrong parsing the server error response").debug(err); rcra.notifications.showErrorMessage(defaultMessage); } return; } rcra.notifications.showErrorMessage("We are very sorry, but it looks like we encountered a problem. \n\n" + req.responseText); } }); $(document).ajaxStart(function () { rcra.blockUI.showWithDelay() }); $(document).ajaxStop(function () { rcra.blockUI.hide(); }); }); Array.prototype.contains = function (element) { return this.indexOf(element) > -1; }; Array.prototype.first = function () { return this[0]; }; String.prototype.format = function() { var str = this; for (var i in arguments) { str = str.replace("{" + i + "}", arguments[i]) } return str } //lookups registry var lookups = {}; var LookupViewModel = function (settings, deferred) { var self = this; self.value = settings.lookupArray; self.loadDeferred = null; self.settings = settings; self.computed = null; var callback = function (data) { if (deferred) { deferred.resolve(data); } }; //do not use pure computed due to side effect. http://knockoutjs.com/documentation/computed-pure.html if(settings.load) { self.load = function() { self.loadDeferred = $.Deferred(); if(self.computed) { self.computed.dispose(); } self.computed = ko.computed(function () { settings.load.call(self, callback); }, self); return self.loadDeferred; } } else { self.load = function () { if (!self.loadDeferred) { self.loadDeferred = $.Deferred(); $.getJSON(settings.url, function (data) { var options = settings.viewModel ? { '': { create: function (options) { return new settings.viewModel(options.data); } } } : {}; ko.mapping.fromJS(data, options, self.value); self.loadDeferred.resolve(); callback(); }); } return self.loadDeferred; } } self.dispose = function() { if(self.computed) { self.computed.dispose(); } } }; function registerLookupWithSettings(name, settings) { if (lookups[name]) { throw name + " is already a registered lookup"; } if (lookups[name + 'VM']) { throw name + "VM is already registed this name must be free to register a lookup. This name holds the control for the lookup."; } lookups[name] = ko.observableArray([]); settings = $.extend({ lookupArray: lookups[name], name: name }, settings); var deferred = $.Deferred(); var test = new LookupViewModel(settings, deferred); lookups[name + 'VM'] = test; return { deferred: deferred, name: name }; } function registerLookup(name, url, viewModel) { registerLookupWithSettings(name, { url: url, viewModel: viewModel }); } function registerLookupCustomLoad(name, loadFunc) { return registerLookupWithSettings(name, { load: loadFunc }); } function loadLookup(name) { var vm = lookups[name + 'VM']; if (vm) { return vm.load(); } else { throw name + ' is not a registered lookup'; } } function cloneLookup(sourceName, targetName, settings) { var vm = lookups[sourceName + 'VM']; settings = $.extend({ url: vm.settings.url }, settings); registerLookupWithSettings(targetName, settings); } function disposeLookup(name) { if(lookups[name + 'VM'] && lookups[name + 'VM'].dispose) { lookups[name + 'VM'].dispose(); } lookups[name + 'VM'] = undefined; lookups[name] = undefined; } //observable array custom functions ko.observableArray.fn.lookup = function(value, compareFunc) { return ko.utils.arrayFirst(this(), function(item) { return compareFunc(item, value); }); }; ko.observableArray.fn.lookupByProp = function(value, property) { return this.lookup(value, function(item, value) { return ko.utils.unwrapObservable(item[property]) == ko.utils .unwrapObservable(value); }); }; ko.observableArray.fn.lookupById = function(value) { return this.lookup(value, function(item, value) { return ko.utils.unwrapObservable(item.id) == ko.utils .unwrapObservable(value); }); }; ko.observableArray.fn.filter = function(value, compareFunc) { return ko.utils.arrayFilter(this(), function(item) { return compareFunc(item, value); }); }; ko.observableArray.fn.filterByProp = function(value, property) { return this.filter(value, function(item, value) { return ko.utils.unwrapObservable(rcra.utils.parseProperty(item, property)) == ko.utils.unwrapObservable(value); }) }; ko.observableArray.fn.searchByProp = function(value, property, caseSenstive) { return this.filter(value, function(item, value) { var parsedProp = ko.utils.unwrapObservable(rcra.utils.parseProperty(item, property)); if(!parsedProp) { return false; } var parsedVal = ko.utils.unwrapObservable(value); if(!caseSenstive) { parsedProp = parsedProp.toLowerCase(); parsedVal = parsedVal.toLowerCase(); } return parsedProp.indexOf(parsedVal) != -1; }); }; ko.observableArray.fn.move = function(from, to) { return this.splice(to, 0, this.splice(from, 1)[0]); }; ko.observableArray.fn.transform = function(transformer) { return rcra.utils.transformArray(this, transformer); } ko.subscribable.fn.previousValue = function () { var oldValue = ko.observable(); this.subscribe(function (_oldValue) { oldValue(_oldValue); }, this, 'beforeChange'); return ko.computed(function () { return oldValue(); }); }; ko.subscribable.fn.subscribeChanged = function (callback) { var oldValue; var observable = this; var beforeChangeSubscr = this.subscribe(function (_oldValue) { oldValue = _oldValue; }, this, 'beforeChange'); var afterChangeSubscr = this.subscribe(function (newValue) { callback.apply(observable, [newValue, oldValue]); }); this.dispose = function () { beforeChangeSubscr.dispose(); afterChangeSubscr.dispose(); }; return this; }; Number.prototype.between = function (a, b, inclusive) { var min = Math.min(a, b), max = Math.max(a, b); return inclusive ? this >= min && this <= max : this > min && this < max; }; //Knockout Component Modal support var popupRegistry = {}; var ModalControl = function(settings) { var self = this; self.name = ko.observable(settings.name); self.open = ko.observable(false); self.title = ko.observable(null); self.openModal = function(params) { params = params || {}; if(params.reset) { self.reset(); } else if(params.data) { var data = params.copy ? ko.mapping.toJS(params.data) : params.data; self.refreshData(data); } if (params.closeCallback && typeof params.closeCallback === "function") { self.closeCallback = params.closeCallback; } self.open(true); }; self.reset = function() { if(!settings.reset) { throw "No reset function was set for modal " + self.name(); } settings.reset(self.popupModel); }; self.refreshData = function(data) { if(self.popupModel().refresh) { self.popupModel().refresh(data, self); } else { var mapSettings = settings.mapSettings || {}; ko.mapping.fromJS(data, mapSettings, self.popupModel) } }; self.closeModal = function(event, data) { self.open(false); postal.publish({ channel: "modal", topic: self.name() + ".close." + event, data: { result: event, name: self.name(), data: data || self.popupModel() } }); if (self.closeCallback) { self.closeCallback(); } } self.popupModel = ko.observable(settings.model ? new settings.model(null, {control: self}) : {}); self.dispose = function() { rcra.utils.dispose(self.popupModel()); } }; function openModal(name, id, settings) { var popupId = id || name; if(!popupRegistry[popupId]) { var params = { id: id }; var modalComponent = $(''); $('#popups').append(modalComponent); var subscription = postal.subscribe({ channel: 'modal', topic: popupId + '.init', callback: function(data, envelope) { popupRegistry[popupId].domElement = modalComponent.get(0); popupRegistry[popupId].openModal(settings); postal.unsubscribe(subscription); } }); ko.applyBindings(null, modalComponent.get(0)); popupRegistry[popupId] = "pending"; } else if (popupRegistry[popupId] == "pending") { //modal is loading } else { popupRegistry[popupId].openModal(settings); } }; function disposeModal(name) { if(popupRegistry[name] && popupRegistry[popupId] != "pending") { if(popupRegistry[name].open()) { $(popupRegistry[name].domElement).on('hidden.bs.modal', function() { ko.removeNode(popupRegistry[name].domElement); popupRegistry[name] = undefined; }); popupRegistry[name].closeModal(); } else { ko.removeNode(popupRegistry[name].domElement); popupRegistry[name] = undefined; } } }; //RCRA Namespace var rcra = { contextPath: null, //Set on the layout page baseApi: '/rest/', accountStatuses: { ACTIVE: "Active", UNVERIFIED: "Unverified", NEWLYREGISTERED: "NewlyRegistered", PENDINGUPDATE: "PendingUpdate", INACTIVE: "Inactive" }, requestStatuses: { PENDING: "Pending", GRANTED: "Granted", DENIED: "Denied" }, profileTypes: { RCRA: "RCRA", CDX: "CDX" }, modal: { global: { mode: { Add: "Add", Edit: "Edit", View: "View" } } }, logger: function () { /*var logModel = function(level, message, data) { var self = this; self.page = window.location.href; self.level = level; self.message = message; self.data = data; self.time = new Date(); return self; }; var loggingLevels = { DEBUG: "Debug", INFO: "Info", ERROR: "Error" }; var config = { _defaultSessionLogKey: "RCRA_CONSOLE_LOGGING", enabledSessionLogLevels: [loggingLevels.ERROR], principalUsername: "", setPrincipalUsername: function (username) { config.principalUsername = username; config.sessionLogKey = config._defaultSessionLogKey + (username ? "_" + username.toUpperCase() : ""); } }; config.sessionLogKey = config._defaultSessionLogKey; var getSessionLog = function (levelFilter, numRecentEntries) { var log = sessionStorage.getItem(config.sessionLogKey); if (log) { log = JSON.parse(log); if (Array.isArray(log)) { if (levelFilter) { var filteredLogEntries = []; forEach(levelFilter, function (level) { var filteredLevelLogs = ko.utils.arrayFilter(log, function(item) { return item.level == level; }); forEach(filteredLevelLogs, function (levelLog) { filteredLogEntries.push(levelLog); }); }); log = filteredLogEntries; } if (numRecentEntries) { if (log.length > numRecentEntries) { log = log.slice(log.length - numRecentEntries); } } return log; } } return []; }; var getSerializedSessionLog = function (levelFilter, numRecentEntries) { var log = getSessionLog(levelFilter, numRecentEntries); if (log.length > 0) { return JSON.stringify(log); } return null; }; var appendSessionLog = function (logEntry) { var sessionLog = getSessionLog(); sessionLog.push(logEntry); var json = JSON.stringify(sessionLog); var append = function () { try { sessionStorage.setItem(config.sessionLogKey, json); return true; } catch(err) { //session storage is likely full if error occurs here sessionStorage.removeItem(config.sessionLogKey); return false; } }; var successfullyAppended = append(); if (!successfullyAppended) { //try once more append(); } }; if (config.enabledSessionLogLevels.includes(loggingLevels.ERROR)) { window.onerror = function (error, url, line) { var errorLogEntry = new logModel(loggingLevels.ERROR, error, url + " at line " + line); appendSessionLog(errorLogEntry); }; } var origConsoleLog = console.log; var origConsoleError = console.error; var origConsoleInfo = console.info; if (config.enabledSessionLogLevels.includes(loggingLevels.DEBUG)) { console.log = function (message, data) { var debugLogEntry = new logModel(loggingLevels.DEBUG, message, data); origConsoleLog.apply(console, arguments); appendSessionLog(debugLogEntry); }; } if (config.enabledSessionLogLevels.includes(loggingLevels.ERROR)) { console.error = function (message, data) { var errorLogEntry = new logModel(loggingLevels.ERROR, message, data); origConsoleError.apply(console, arguments); appendSessionLog(errorLogEntry); }; } if (config.enabledSessionLogLevels.includes(loggingLevels.INFO)) { console.info = function (message, data) { var infoLogEntry = new logModel(loggingLevels.INFO, message, data); origConsoleInfo.apply(console, arguments); appendSessionLog(infoLogEntry); }; } var getSessionLogSize = function () { var key = config.sessionLogKey; var xLen = ((sessionStorage[key].length + key.length) * 2); return key.substr(0, 50) + " = " + (xLen / 1024).toFixed(2) + " KB"; };*/ var result = { consoleLoggingEnabled: true, localSessionLoggingEnabled: true, debug: function (msg, obj) { if (result.consoleLoggingEnabled) { //Needed for IE9 and previous browsers that don't have console defined if (!window.console) console = { log: function () { } }; } else { return result; } if (console !== undefined) { if (obj) { console.log(msg, obj); } else { console.log(msg); } } return result; }/*, getSerializedSessionLog: getSerializedSessionLog, getSessionLog: getSessionLog, getSessionLogSize: getSessionLogSize, loggingLevels: loggingLevels, config: config*/ }; return result; }(), lookups: { states: function (country, activityLocation, callback) { var country = ko.mapping.toJS(country); var activityLocation = ko.unwrap(activityLocation); if (!country || !country.code) { return; } if (country.code == "US") { if (activityLocation == "NN") { $.getJSON(rcra.contextPath + "/rest/handler-lookup/tribal-states/" + activityLocation, callback); } else { $.getJSON(rcra.contextPath + "/rest/public/lookup/states", callback); } } else { $.getJSON(rcra.contextPath + "/rest/public/lookup/foreign-states/" + country.id, callback); } }, //note: activity location is not used in this function but passed in to be consistent with function above statesNoTribal: function (country, activityLocation, callback) { var country = ko.mapping.toJS(country); if (!country || !country.code) { return; } if (country.code === "US") { $.getJSON(rcra.contextPath + "/rest/public/lookup/states", callback); } else { $.getJSON(rcra.contextPath + "/rest/public/lookup/foreign-states/" + country.id, callback); } }, getLookupValue: function (code, lookup) { var match = ko.utils.arrayFirst(ko.unwrap(lookup), function(obj) { return ko.unwrap(obj.code) == ko.unwrap(code); }); return match ? match : {}; }, cityStates: function (zipCode, callback ) { $.ajax({ url: rcra.contextPath + "/rest/public/lookup/city-state/" + zipCode, method: "GET", global: false, success: callback }); } }, formatting: { removeSpaces: function (str) { if (str) { return str.replace(/\s+/g, ''); } return str; }, formatZip:function(zip) { if (zip == null) return ""; var z = zip.trim().replace(/\D/g,""); if(z.length==7){ return z.substr(0,4)+"-"+z.substr(4); } else if(z.length==9){ return z.substr(0,5)+"-"+z.substr(5); } else return z; }, formatPhone: function(phone) { return phone.substring(0,3).concat('-').concat(phone.substring(3,6)).concat('-') .concat(phone.substring(6,10)); }, formatNumber: function(val){ var string = val.toString(); return string.replace(/\B(?=(\d{3})+(?!\d))/g, ','); } }, bootstrap: { closeModal: function (target, callback) { setTimeout(function () { var modalEl = $(target).closest(".modal"); if (modalEl.length <= 0){ $(target).find(".modal").modal("hide") } // Hide Modal modalEl.modal("hide") if (callback) { callback(); } }, 250); }, showModal: function (modal, callback) { $(modal).modal('show'); if (callback) { callback(); } } }, knockout: { getValue: function (o) { return (typeof o === 'function' ? o() : o); }, clone: function (o, toJsOptions, fromJsOptions) { if (!toJsOptions) { toJsOptions = {}; } if (!fromJsOptions) { fromJsOptions = {}; } var mappedObject = ko.mapping.fromJS(ko.mapping.toJS(o, toJsOptions), fromJsOptions); if (rcra.knockout.isObservableArray(mappedObject)) { return ko.observableArray(rcra.cleanKoProperties(ko.unwrap(mappedObject))); } else if (ko.isObservable(mappedObject)) { return ko.observable(rcra.cleanKoProperties(ko.unwrap(mappedObject))); } return rcra.cleanKoProperties(mappedObject); }, isObservableArray: function(instance) { return !!instance && typeof instance["remove"] === "function" && typeof instance["removeAll"] === "function" && typeof instance["destroy"] === "function" && typeof instance["destroyAll"] === "function" && typeof instance["indexOf"] === "function" && typeof instance["replace"] === "function"; }, clearObject: function (o) { var obj = ko.unwrap(o); for (var property in obj) { if (property.indexOf("_") == 0) { continue; } if (obj.hasOwnProperty(property)) { var value = obj[property]; switch(typeof value) { case "object": rcra.knockout.clearObject(value); break; case "function": if(rcra.knockout.isObservableArray(value)) { if (ko.unwrap(value).length > 0) { value.removeAll(); } }else if (ko.isObservable(value)) { typeof ko.unwrap(value) == "boolean" ? value(false) : value(undefined); } break; default: break; } } } } }, validation: { setRequiredElementIndicator: function (element) { $(element).closest("div.form-group").addClass("required"); }, removeRequiredElementIndicator: function (element) { $(element).closest("div.form-group").removeClass("required"); }, /** * For a given validation group it will loop through all elements in that group and set the modified property * to false in order to hide validation messages on the ui. * @param validationGroup */ resetErrorMessages: function(validationGroup) { if(validationGroup) { validationGroup.forEach(function(property) { if(property.isModified) { property.isModified(false); } }); } }, removeValidations: function(validationGroup) { if(validationGroup) { validationGroup.forEach(function(property) { if(property.rules) { property.rules.removeAll(); } }) } }, removeRule: function(field, rule) { if(field.rules) { field.rules.remove(field.rules.lookupByProp(rule, 'rule')); } }, isRequired: function(field) { console.log("checking if field is required", field) return !!(field.rules && field.rules.lookupByProp('required', 'rule')); }, whenValidationsComplete: function(errors, callback) { //prevent running multiple times var ran = false; var computed = ko.computed(function() { var validatingObservables = errors.filter(function(o){ return o.isValidating && o.isValidating() }); if(validatingObservables.length == 0) { if(!ran) { ran = true; callback(); if(computed) { //dispose when done if this all resolved synchronously computed will be undefined here and will be disposed. computed.dispose(); } } } }).extend({rateLimit: {timeout: 50, method: "notifyWhenChangesStop" }}); if(ran) { //if the computed resolved synchronously dispose it computed.dispose(); } }, isWarning: function (observable) { if (!observable.error) { return false; } var error = observable.error.peek(); var match = ko.utils.arrayFirst(observable.rules(), function (item) { var rule = ko.validation.rules[item.rule]; var message = item.message || (!!rule ? ko.validation.rules[item.rule].message : ""); return error === message; }); return match && match.type === "warning"; } }, notifications: { showAlertDialog: function (title, message, options) { options = $.extend({}, { type: BootstrapDialog.TYPE_DEFAULT, buttons: [], showCloseButton: true, closeButtonText: "Cancel", cssClass: '', size: BootstrapDialog.SIZE_NORMAL, template: { id: null, vm: null }, dialogId:null, closeId:{id:null}, onhide: function () {}, onshow: function () {}, onshown: function () {}, callback: function() {} }, options); if (options.showCloseButton == true) { options.buttons.push({ id: options.closeId.id, label: options.closeButtonText, action: function (dialogItself) { dialogItself.close(); } }); } if (options.template.id != null) { message = ''; } var dialogSettings = { type: options.type, title: title, message: message, buttons: options.buttons, size: options.size, cssClass: options.cssClass, closeByBackdrop: options.closeByBackdrop, closeByKeyboard: options.closeByKeyboard, closable: options.closable, onhide: options.onhide, onshow: options.onshow, onshown: options.onshown, callback: options.callback }; if (options.dialogId != null) {dialogSettings.id = options.dialogId}; var dialogInstance = BootstrapDialog.show(dialogSettings); if (options.template.vm != null){ var isBound = !!ko.dataFor(dialogInstance.getModal()[0]); if (!isBound) ko.applyBindings(options.template.vm, dialogInstance.getModal()[0]); } return dialogInstance; }, closeAllAlerts: function () { BootstrapDialog.closeAll(); }, showErrorMessage: function (msg, options) { options = $.extend({}, { title: "", //please note: we have these set to 0 so that users will have to close all error messages by hand. timeOut: 0, extendedTimeOut: 0, closeButton: true, newestOnTop: true, showMethod: "slideDown", hideMethod: "slideUp", closeMethod: "slideUp", preventDuplicates: true, positionClass: "toast-top-full-width", target: "#status-messages" }, options); return toastr.error(msg, options.title, options); }, showSuccessMessage: function (msg, options) { options = $.extend({}, { title: "", timeOut: 5000, extendedTimeOut: 5000, closeButton: true, newestOnTop: true, showMethod: "slideDown", hideMethod: "slideUp", closeMethod: "slideUp", preventDuplicates: true, positionClass: "toast-top-full-width", target: "#status-messages" }, options); return toastr.success(msg, options.title, options); }, showWarnMessage: function (msg, options) { options = $.extend({}, { title: "", timeOut: 5000, extendedTimeOut: 5000, closeButton: true, newestOnTop: true, showMethod: "slideDown", hideMethod: "slideUp", closeMethod: "slideUp", preventDuplicates: true, positionClass: "toast-top-full-width", target: "#status-messages" }, options); return toastr.warning(msg, options.title, options); }, showInfoMessage: function (msg, options) { options = $.extend({}, { title: "", timeOut: 5000, extendedTimeOut: 5000, closeButton: true, newestOnTop: true, showMethod: "slideDown", hideMethod: "slideUp", closeMethod: "slideUp", preventDuplicates: true, positionClass: "toast-top-full-width", target: "#status-messages" }, options); return toastr.info(msg, options.title, options); }, messageTypes: { SUCCESS: "Success", INFO: "Info", WARNING: "Warning", ERROR: "Error" }, showMessage: function (messageType, msg, options) { switch (messageType) { case rcra.notifications.messageTypes.SUCCESS: rcra.notifications.showSuccessMessage(msg, options); break; case rcra.notifications.messageTypes.INFO: rcra.notifications.showInfoMessage(msg, options); break; case rcra.notifications.messageTypes.WARNING: rcra.notifications.showWarnMessage(msg, options); break; case rcra.notifications.messageTypes.ERROR: rcra.notifications.showErrorMessage(msg, options); break; default: alert("Invalid notification message type provided!"); } }, hideMessages: function (useAnimation) { if (useAnimation) { toastr.clear(); } else { toastr.remove(); } } }, blockUI: { show: function (msg) { var isBlocked = $(window).data("blockUI.isBlocked"); if (isBlocked && isBlocked == 1) { return; } $.blockUI({ message: msg, css: rcra.blockUI.settings.css, baseZ: 9000 }); //Hides block ui elements from html2canvas screenshots $('.blockUI').attr('data-html2canvas-ignore', 'true') }, showWithDelay: function (msg, delay) { if (!delay) { delay = rcra.blockUI.settings.delayInterval; } if (rcra.blockUI.settings.enableDelay == true) { rcra.blockUI.timeoutInstance = setTimeout(function () { rcra.blockUI.show(msg); }, delay); } else { rcra.blockUI.show(msg); } }, hide: function () { if (rcra.blockUI.timeoutInstance) { clearTimeout(rcra.blockUI.timeoutInstance); } $.unblockUI(); }, settings: { css: { border: 'none', padding: '15px', backgroundColor: '#000', '-webkit-border-radius': '10px', '-moz-border-radius': '10px', opacity: .5, color: '#fff' }, enableDelay: true, delayInterval: 3000 } }, xhrSettings: { mimeTypes: { JSON: "application/json", XML: "application/xml", TEXT: "text/plain" }, setJsonAcceptHeader: function (xhr) { xhr.setRequestHeader('Accept', rcra.xhrSettings.mimeTypes.JSON); }, setXmlAcceptHeader: function (xhr) { xhr.setRequestHeader('Accept', rcra.xhrSettings.mimeTypes.XML); } }, cookies: { _cookiePrefix: "_RCRA_", getCookie: function (cookieName) { var cookie = Cookies.get(rcra.cookies._cookiePrefix + cookieName); if (!cookie) { console.warn("Cookie does not exist with name " + cookieName); return null; } var cookieData = JSON.parse(cookie); return cookieData; }, cookieExists: function (cookieName) { return Cookies.get(rcra.cookies._cookiePrefix + cookieName) ? true : false; }, setCookie: function (cookieName, data, options) { Cookies.set(rcra.cookies._cookiePrefix + cookieName, data, options); }, deleteCookie: function (cookieName) { Cookies.remove(rcra.cookies._cookiePrefix + cookieName); }, clearCookies: function (allCookies) { forEach(Object.keys(Cookies.get()), function (cookieName) { if (allCookies === true) { Cookies.remove(cookieName); } else { if (cookieName.slice(0, rcra.cookies._cookiePrefix.length) === rcra.cookies._cookiePrefix) { Cookies.remove(cookieName); } } }); } }, storage: { clearByPrefix: function (prefix){ //yes, we are checking of storage is supported if(typeof Storage !== "undefined"){ var arr = []; // Array to hold the keys // Iterate over localStorage and insert the keys that meet the condition into arr for (var i = 0; i < localStorage.length; i++){ if (localStorage.key(i).startsWith(prefix)) { arr.push(localStorage.key(i)); } } // Iterate over arr and remove the items by key for (var i = 0; i < arr.length; i++) { localStorage.removeItem(arr[i]); } } } }, resetModel: function (model) { for (var property in model) { if (model.hasOwnProperty(property)) { var obj = model[property]; if (rcra.knockout.isObservableArray(obj)) { obj.removeAll(); } else if (ko.isObservable(obj)) { obj(undefined); } else if (typeof obj === 'object') { rcra.resetModel(obj); } } } }, // Built for exceptions to be obj, might add array as well resetModelValidation: function (model, exceptions) { for (var property in model) { if (model.hasOwnProperty(property)) { if (exceptions && exceptions[property]){ continue; } var obj = model[property]; if (typeof obj.isModified === "function"){ obj.isModified(false); } } } }, pruneEmptyObjects: function (obj) { return function prune(current) { _.forOwn(current, function (value, key) { if (_.isUndefined(value)) { delete current[key]; } else if (_.isNull(value)) { delete current[key]; } else if (_.isNaN(value)) { delete current[key]; } else if ((_.isString(value) && _.isEmpty(value))) { delete current[key]; } else if ((_.isObject(value) && Object.prototype.toString.call(value) === "[object Date]" )) { if (!value) { delete current[key]; } } else if ((_.isObject(value) && _.isEmpty(prune(value)))) { delete current[key]; } }); // remove any leftover undefined values from the delete // operation on an array if (_.isArray(current)) _.pull(current, undefined); return current; }(_.cloneDeep(obj)); // Do not modify the original object, create a clone instead }, cleanKoProperties: function(object){ for (var property in object) { if (property.indexOf("_") == 0) { delete object[property]; } else if (object.hasOwnProperty(property)) { var value = ko.unwrap(object[property]); if (!value) { continue; } if (value.constructor === Array) { ko.utils.arrayForEach(value, function(item) { rcra.cleanKoProperties(item); }); } else if (value.constructor === Object || (typeof value === 'function') || (typeof value === 'object')) { rcra.cleanKoProperties(value); } } } delete object.errors; delete object.isValid; //adding some sugar return object; }, createNamespace: function (namespace) { var nsparts = namespace.split("."); var parent = rcra; for (var i = 0; i < nsparts.length; i++) { var partname = nsparts[i]; if (typeof parent[partname] === "undefined") { parent[partname] = {}; } parent = parent[partname]; } return parent; }, smartPopoverPlacement: function (context, source) { var classList = $(context).attr('class').split(/\s+/); var cssClasses = { "huge": 500, "large": 450, "medium": 400 }; var popoverWidth = 350; var popoverHeight = 350; classList.forEach(function(className){ switch (className) { case "huge": popoverWidth = cssClasses.huge; break; case "large": popoverWidth = cssClasses.large; break; case "medium": popoverWidth = cssClasses.medium; break; } }); var availableOnRight = $(window).width() - $(source).offset().left; var availableOnLeft = $(source).offset().left; var availableOnTop = $(source).offset().top; if (availableOnRight > popoverWidth) { return 'right'; } else if (availableOnLeft > popoverWidth){ return 'left'; } else { if (availableOnTop > popoverHeight) { return 'top'; } else { return 'bottom'; } } }, handlers: { search: function(jsonConfig) { var searchApi = rcra.baseApi + 'handler-search/handlers'; return $.ajax({ url: rcra.contextPath + searchApi, data: jsonConfig, dataType: 'json', type: 'POST', contentType: rcra.xhrSettings.mimeTypes.JSON, beforeSend: rcra.xhrSettings.setJsonAcceptHeader, }); } }, pager: { path: function(page) { if (page == null || page.originalRoute() == null) { return []; } return rcra.pager.path(page.getParentPage()).concat(page.originalRoute()); }, currentPagePath: function() { try { return rcra.pager.path(pager.getActivePage()); } catch (e) { return []; } }, parentPath: function(levels) { if(!levels) { levels = 1; } var path = rcra.pager.currentPagePath(); return path.slice(0, path.length - levels); }, navigateUp: function(levels) { pager.navigate(rcra.pager.buildPath(rcra.pager.parentPath(levels))); }, buildPath: function(route) { return pager.Href.hash + route.join('/'); }, pageRoute: function(page) { if(!page|| !page.element) { return []; }; return rcra.pager.pageRoute(page.getParentPage()).concat(page); }, currentPageRoute: function() { try { return rcra.pager.pageRoute(pager.getActivePage()); } catch (e) { return []; } }, navigatePath: function(path) { return pager.navigate(rcra.pager.buildPath(path)); }, isOnRoute: function() { var pageRoute = rcra.pager.currentPageRoute(); if(arguments.length > pageRoute.length) { return false; } for(var i = 0; i < arguments.length; ++i) { if(arguments[i] != pageRoute[i].getCurrentId()) { return false; } } return true; } }, esri: { convertAddressToCandidate: function (address) { let formattedAddress = ko.unwrap(address.address2) ? ko.unwrap(address.address1).trim() + ' ' + ko.unwrap(address.address2).trim() : ko.unwrap(address.address1); return JSON.stringify({ number: ko.unwrap(address.streetNumber), address: formattedAddress, city: ko.unwrap(address.city), state: ko.unwrap(address.state) ? ko.unwrap(address.state().description) : null, zip: ko.unwrap(address.zip), country: ko.unwrap(address.country) ? ko.unwrap(address.country().code) : null }); }, mapAddressToModel: function (address, model, lookups, fillState, module, additionalModel) { if (module === 'pcb') { return this.mapAddressToPcbModel(address, model, lookups, fillState, additionalModel); } else { return this.mapAddressToHandlerModel(address, model, lookups, fillState); } }, mapAddressToHandlerModel: function (address, model, lookups, fillState) { let addrAttributes = address.attributes; //use StAddr as a whole if it exists or try to build one with the address attributes let address1String = addrAttributes.StAddr ? addrAttributes.StAddr : $.grep([addrAttributes.StName, addrAttributes.StType, addrAttributes.StDir], function (attr) { return attr != ""; }).join(" "); //if user is already using the street number field then use it otherwise prepend street number into address1 if (rcra.utils.parseProperty(model, 'streetNumber')) { model.streetNumber(addrAttributes.AddNum.toUpperCase()); //remove street number from string if it is present let regex = new RegExp(addrAttributes.AddNum, 'g'); address1String = address1String.replace(regex, '').trim(); model.address1(address1String.toUpperCase()); } else { //if StAddr exists then assume the address number is already present addrAttributes.StAddr ? model.address1(address1String.toUpperCase()) : model.address1($.grep([addrAttributes.AddNum, address1String], function (attr) { return attr != ""; }).join(" ").toUpperCase()); } if (addrAttributes.SubAddr) { model.address2(addrAttributes.SubAddr.toUpperCase()); } let formattedZip = addrAttributes.PostalExt ? addrAttributes.Postal + '-' + addrAttributes.PostalExt : addrAttributes.Postal; model.zip(formattedZip.toUpperCase()); if (addrAttributes.City) { model.city(addrAttributes.City.toUpperCase()); } if (addrAttributes.Country) { model.country(lookups.countries.lookupByProp(addrAttributes.Country.substring(0, 2), 'code')); } if (fillState && addrAttributes.RegionAbbr) { model.state(lookups.states.lookupByProp(addrAttributes.RegionAbbr, 'code')); } if (addrAttributes.Subregion != "" && addrAttributes.Region != "" && addrAttributes.Subregion != addrAttributes.Region) { model.county(addrAttributes.SubRegion.toUpperCase()); } //X and Y reference graphing axes rather than direct lat & long if (address.location) { model.latitude(Number(address.location.y).toFixed(6)); model.longitude(Number(address.location.x).toFixed(6)); model.gisOrigin(lookups.gisOrigins.lookupByProp("AG", "code")); } return model; }, mapAddressToPcbModel: function (address, model, lookups, fillState, additionalModel) { let addrAttributes = address.attributes; //use StAddr as a whole if it exists or try to build one with the address attributes let address1String = addrAttributes.StAddr ? addrAttributes.StAddr : $.grep([addrAttributes.StName, addrAttributes.StType, addrAttributes.StDir], function (attr) { return attr != ""; }).join(" "); //if user is already using the street number field then use it otherwise prepend street number into address1 if (rcra.utils.parseProperty(model, 'streetNumber')) { model.streetNumber(addrAttributes.AddNum.toUpperCase()); //remove street number from string if it is present let regex = new RegExp(addrAttributes.AddNum, 'g'); address1String = address1String.replace(regex, '').trim(); model.address1(address1String.toUpperCase()); } else { //if StAddr exists then assume the address number is already present addrAttributes.StAddr ? model.address1(address1String.toUpperCase()) : model.address1($.grep([addrAttributes.AddNum, address1String], function (attr) { return attr != ""; }).join(" ").toUpperCase()); } if (addrAttributes.SubAddr) { model.address2(addrAttributes.SubAddr.toUpperCase()); } let formattedZip = addrAttributes.PostalExt ? addrAttributes.Postal + '-' + addrAttributes.PostalExt : addrAttributes.Postal; model.zip(formattedZip.toUpperCase()); if (addrAttributes.City) { model.city(addrAttributes.City.toUpperCase()); } if (addrAttributes.Country) { model.country(lookups.countries.lookupByProp(addrAttributes.Country.substring(0, 2), 'code')); } if (fillState && addrAttributes.RegionAbbr) { model.state(lookups.states.lookupByProp(addrAttributes.RegionAbbr, 'code')); } //X and Y reference graphing axes rather than direct lat & long if (additionalModel) { additionalModel.latitude(Number(address.location.y).toFixed(6)); additionalModel.longitude(Number(address.location.x).toFixed(6)); additionalModel.gisOrigin(rcra.pcb.constants.gisOrigins.AutomaticallyGeocoded); } return model; } }, utils: { fixDataTables: function(page){ ko.utils.arrayForEach($.fn.dataTable.tables(), function(table){ rcra.utils.fixDataTable(table); }); }, fixDataTable: function(table){ $(table).css( 'width', '100%' ); if ($(table).DataTable().responsive) { // $(table).DataTable().responsive.rebuild(); $(table).DataTable().columns.adjust(); $(table).DataTable().responsive.recalc(); } $(table).DataTable().off('responsive-display'); $(table).DataTable().on('responsive-display', function ( e, datatable, row, visible, update ) { if (!visible || !ko.dataFor(row.node()) || !row.child()) return; if (ko.dataFor(row.child()[0])) ko.cleanNode(row.child()[0]); ko.applyBindings(ko.contextFor(row.node()), row.child()[0]); }); $("#" + $(table).context.id + "_filter input[type='search']").attr("maxlength", "3998"); }, bindRowDataToResponsiveColumns: function (table, columns, vm) { $(table).DataTable().on('responsive-display', function(e, datatable, row, visible, update) { if(row.child()) { ko.utils.arrayForEach(columns, function(col) { ko.applyBindings(vm || row.data(), row.child().find('[data-dt-column="' + col + '"]')[0]) }) } }) }, makeDialogTablesResponsive: function() { //may not want to use at times, data may be cleared var tables = $('.dialog-table'); ko.utils.arrayForEach(tables, function(table) { new $.fn.dataTable.Responsive($(table).DataTable()); }); }, addDataTableSearchInfoIcon: function (content, context, dataTable) { var tableId = $(dataTable).attr('id'); var $searchContainer = $("#" + tableId + "_filter"); var $searchBox = $searchContainer.find("input[type='search']"); var $searchIcon = $(''); $searchIcon.insertBefore($searchBox); //Only apply the binding handler instead of a new context in order to prevent multiple binding contexts on the same element ko.applyBindingAccessorsToNode($searchIcon[0], { popover: function () { return { options: { container: 'body', content: content, trigger: 'hover focus', placement: rcra.smartPopoverPlacement } } } }); }, fixSelect2Dropdowns: function() { $('.select2-focusser').attr('role', 'combobox'); }, parseProperty: function(object, prop, returnObservable) { var props = prop.split('.'); var target = ko.utils.unwrapObservable(object); for (var i = 0; i < props.length; ++i) { if(!target) { return target; } if(returnObservable && i == props.length - 1) { target = target[props[i]] } else { target = ko.utils.unwrapObservable(target[props[i]]); } } return target; }, splitCamelCaseString: function(str) { str = ko.unwrap(str); if (str === "" || str === undefined) return ""; return str.split(/(?=[A-Z])/).join(" "); }, capitalizeString: function(str){ str = ko.unwrap(str); if (str === "" || str === undefined) return ""; return str.charAt(0).toUpperCase() + str.slice(1) }, addNbsp: function() { return String.fromCharCode(160); }, dateTime: function (date) { var unwrappedDate = ko.utils.unwrapObservable(date); if (unwrappedDate) { return moment(unwrappedDate).format('MM/DD/YYYY hh:mm A'); } return ''; }, dateTimeNoonUTC: function (date) { if (date) { return moment(date).utc().startOf('day').add(12, 'hours').format(); } return ''; }, date: function (date) { var unwrappedDate = ko.utils.unwrapObservable(date); if (unwrappedDate) { return moment(unwrappedDate).format('MM/DD/YYYY'); } return ''; }, dateMonth: function(date) { var unwrappedDate = ko.utils.unwrapObservable(date); if(unwrappedDate) { return moment(unwrappedDate).format('MMMM YYYY'); } return ''; }, dateYear: function(date) { var unwrappedDate = ko.utils.unwrapObservable(date); if(unwrappedDate) { return moment(unwrappedDate).format('YYYY'); } return ''; }, currency: function(amount) { return "$" + amount.toFixed(2).replace(/(\d)(?=(\d{3})+\.)/g, "$1,"); }, number: function(num) { var unwrappedNum = ko.utils.unwrapObservable(num); if (unwrappedNum){ unwrappedNum = unwrappedNum.toString(); return unwrappedNum.replace(/\B(?=(\d{3})+(?!\d))/g, ','); } return "0"; }, isNumeric: function (val) { return /^\d+$/.test(ko.unwrap(val)); }, multiselectComputed: function(propList, lookup) { return ko.pureComputed({ read: function () { var codes = []; ko.utils.arrayForEach(ko.utils.unwrapObservable(propList), function (item) { codes.push(rcra.utils.parseProperty(item, 'code')); }); return codes; }, write: function (val) { var toRemove = []; ko.utils.arrayForEach(ko.utils.unwrapObservable(propList), function (item) { var code = rcra.utils.parseProperty(item, 'code'); var index = val.indexOf(code); if (index < 0) { toRemove.push(item); } else { val.splice(index, 1); } }); propList.removeAll(toRemove); if (val.length > 0) { ko.utils.arrayForEach(val, function (item) { propList.push(lookup.lookupByProp(item, 'code')); }); } } }); }, panelComputed: function(selectedPanel, panelName) { return ko.pureComputed({ read: function() { return selectedPanel() == panelName; }, write: function(show) { if(show) { selectedPanel(panelName); } else if(selectedPanel() == panelName) { selectedPanel(null); } } }) }, mapping: { lookupConfig: { update: function(options) { return options.data ? ko.observable(new BaseLookupModel(options.data)) : null; } }, lookupListConfig: { create: function(options) { return options.data ? new BaseLookupModel(options.data) : ko.observableArray([]); } } }, sequence: { current: 0, next: function() { return ++rcra.utils.sequence.current; } }, disposeList: function(list) { ko.utils.arrayForEach(ko.utils.unwrapObservable(list), rcra.utils.dispose); }, dispose: function(obj) { if(obj) { if (obj.dispose) { obj.dispose(); } else if(obj.unsubscribe) { obj.unsubscribe(); } } }, getUrlParam: function(name) { var results = new RegExp('[\?&]' + name + '=([^]*)') .exec(window.location.search); return (results !== null) ? results[1] || 0 : false; }, htmlToText: function (html) { html = ko.unwrap(html); html = html.replace(/