var isMicrosoft = !!window.MSInputMethodContext;

//http://stackoverflow.com/questions/7918868/how-to-escape-xml-entities-in-javascript
String.prototype.decodeHTML = function () {
  return this.replace(/&apos;/g, "'").replace(/&quot;/g, '"').replace(/&gt;/g, '>').replace(/&lt;/g, '<').replace(/&amp;/g, '&');
};

$(".DpsTableCell").hover(function() {
  $(this).addClass("DpsTableCellFirstHoverDone");
});

function makeASCIIOnly(evt, elem) {
  if(evt.type == "keyup" && (evt.keyCode == 16 || evt.keyCode == 9 || (evt.keyCode >= 35 && evt.keyCode <= 40)))
    return;

  var whiteList = /^[ -\x7D]+$/;
  var value = $(elem).val();

  if(value.length && !whiteList.test(value)) {
    value = value.replace(/[^ -\x7D]/g, "");
    $(elem).val(value);
  }
}

function CheckRequirement(LHSName, RHSElemName, predicate, message) {
  var LHSElem = $("[name=" + LHSName + "]")[0];
  var LHSVal = LHSElem.value;
  var RHSElem = $("[name=" + RHSElemName + "]")[0];
  var RHSVal = RHSElem.value;

  if (LHSVal && !predicate(LHSVal, RHSVal)) {
    LHSElem.setCustomValidity(message);
    alert(message); //firefox setCustomValidity message doesn't appear to show up for me. probs a browser issue. So just stick an alert box in as well.
    return false;
  }
}

// b - Number to validate or add checksum digit to, as Number or as String. y - optional; if true, add checksum digit to b, otherwise validate. t, e, s, u - placeholders
function CheckLuhn(b, y, t, e, s, u) {

  // accept the empty string as special case.
  if ( b.length == 0 )
	  return true;
	  
  if (b.match(/\D+/g) != null || b.match(/\d{13,19}/g) == null)
    return false;

  s = 0;
  u = y ? 1 : 2;
  // ensure b is a string and iterate over digits in b from right to left
  for (t = (b = b + '').length; t--;) {
    e = b[t] * (u ^= 3); // if we are at an "even" position, double the digit
    s += e - (e > 9 ? 9 : 0); // reduce to one digit and add up
  }
  t = 10 - (s % 10 || 10); // calculate the difference to modulo10
  return y ? b + t : !t; // if calculating checksum, concat; if validating, 0 is a pass
}

function IsThereALuhnPresent(text) {
  var matcher = /^\D*(\d{13,19}).*$/;
  var matches = matcher.exec(text);

  if (matches == null)
    return false;

  return CheckLuhn(matches[1]);
}

function EnsureAlphanumeric(text) {
  return text.match(/\W+/g) == null;
}

function EnsureAlphanumericWithPotentialSpaces(text) {
  return text.match(/^(\s*\w+\s*)+$/) != null;
}

function EnsureDigits(text) {
  return text.match(/\D+/g) == null;
}

function ValidateCardHolderName() {
  $("input[name=CardHolderName]").each(function() {
    var cardHolderName = $.trim($(this).val());
    if (cardHolderName.length == 0 ||
      cardHolderName.match(/[^-A-Za-z01-9!"#%&'\(\)\*\+,. ]/g) != null ||
      IsThereALuhnPresent(cardHolderName) == true) {
      this.setCustomValidity("Value is invalid, please try again. Example: John Smith");
    }
    return true;
  });
}

function ResetFieldsetControls() {
  //finds nearest parent fieldset, loops through all controls and 'clears' them
  var fieldSet = $('[name=Clear]').closest("fieldset");

  fieldSet.find('input:not([readonly])').each(function() {
    //this should fetch pretty much everything needed apart from dropdowns. 
    this.value = '';
  });

  fieldSet.find('.DpsFieldAmountNumeral').each(function() { this.value = '0'; });
  fieldSet.find('.DpsFieldAmountFraction').each(function() { this.value = '00'; });

  //select:not([class^=DpsFieldDate])
  fieldSet.find('select').each(function() {
    //fetch dropdowns. If a dropdown has no blank option, then don't touch it (can't really 'clear' it). Search dropdowns generally would have mustselect="0", but dates usually wouldn't have a blank option 
    $(this).find('option').each(function() {
      if (this.value === '')
        $(this).prop('selected', true);
    });
  });

  //clear datetime filters... make the assumption that startDay/Month/Year will precede endDay/Month/Year in the <fieldset>s
  var startVersion = true; //is this the startDay/Month/Year or the endDay/Month/Year
  var startDate = new Date();
  var endDate = new Date(startDate.getTime() + 1 * 24 * 60 * 60 * 1000); //+ 1 day. let javascript handle the day/month/year overflow logic.

  fieldSet.find('select.DpsFieldDateDay').each(function() {
    //if the datetime allows a blank value, then it should have already been set to that value by the 2nd selector::find in this method. So all is good.
	  if (startVersion && $(this).val() != '')
	  {
		  this.selectedIndex = startDate.getDate() - 1;
		   //Clear the Custom Validity set by PxValidateDateRange
		  this.setCustomValidity("");
	  }
      else if ($(this).val() != '')
      this.selectedIndex = endDate.getDate() - 1;
	startVersion = !startVersion;
	
  });
  
  startVersion = true;
  
  fieldSet.find('select.DpsFieldDateMonth').each(function() {
    //if the datetime allows a blank value, then it should have already been set to that value by the 2nd selector::find in this method. So all is good.
    if (startVersion && $(this).val() != '')
      this.selectedIndex = startDate.getMonth();
    else if ($(this).val() != '')
      this.selectedIndex = endDate.getMonth();
    startVersion = !startVersion;
  });

  startVersion = true;

  fieldSet.find('select.DpsFieldDateYear').each(function() {
    //if the datetime allows a blank value, then it should have already been set to that value by the 2nd selector::find in this method. So all is good.
    if (startVersion && $(this).val() != '')
      this.value = startDate.getYear() + 1900;
    else if ($(this).val() != '')
      this.value = endDate.getYear() + 1900;
    startVersion = !startVersion;
  });
  
  //these are actually Reset to '00' (not related to current time)
  fieldSet.find('select.DpsFieldDateHour, select.DpsFieldDateMin, select.DpsFieldDateSec').each(function() {
    this.selectedIndex = 0;
  });
}

function isMasterCard(cn) {
  if (cn.length < 8)
    return false;

  var prefixStr2 = cn.slice(0, 2);
  var prefix2 = parseInt(prefixStr2, 10);
  if ((prefix2 >= 51) && (prefix2 <= 55))
    return true;

  var prefixStr8 = cn.slice(0, 8);
  var prefix8 = parseInt(prefixStr8, 10);
    return (prefix8 >= 22210000) && (prefix8 <= 27209999);
}

function isJCBCard(cn) {
	if (cn.length < 2)
		return false;
	var prefixStr2 = cn.slice(0, 2);
	var prefix2 = parseInt(prefixStr2, 10);
	return (prefix2 == 35);	
}

function isVisaCard(cn) {
	if (cn.length < 1)
		return false;
	var prefixStr2 = cn.slice(0, 1);
	var prefix2 = parseInt(prefixStr2, 10);
	return (prefix2 == 4);
}

function isAmexCard(cn) {
	if (cn.length < 2)
		return false;
	var prefixStr2 = cn.slice(0, 2);
	var prefix2 = parseInt(prefixStr2, 10);
	return (prefix2 == 34) || (prefix2 == 37) ;
}

function ValidateCvc2() {

	cardElement = $("[name=CardNumber]")[0];
	cn = '';
	if (cardElement.value) {
		cn = cardElement.value;
	}
	else if (cardElement.innerHTML) {
		cn = cardElement.innerHTML;
	}

	$("input[name=Cvc2]").each(function () {
		var cvc2 = $.trim($(this).val());
		if (cn.length > 1 && ((isVisaCard(cn) || isMasterCard(cn) || isJCBCard(cn)) && cvc2.length != 3) || ((isAmexCard(cn)) && cvc2.length != 4) )
		{
			this.setCustomValidity("Your card number requires a valid cvc value.");
		}	
		return true;
	});
}

function PxValidateIpAddressElement(id, index, additionalParam) {
  var ve = document.getElementById(id);
  if (ve && ve.value && ve.value.length != 0 && ve.value > 255)
    ve.setCustomValidity('Value must be between 0 and 255, please try again.');
}

function PxValidateIpAddressElement(name) {
  var ve = document.getElementsByName(name)[0];
  if (ve && ve.value && ve.value.length != 0 && ve.value > 255)
    ve.setCustomValidity('Value must be between 0 and 255, please try again.');
}

function PxRemovePxPayInternalFields()
{
  $("[name=IIdMenuItemSelected]").remove();
  $("[name=IId]").remove();
  $("[name=IIdVal]").remove();
  $("[name=IIdMenuShown]").remove();
  $("[name=CurrentTab]").remove();
  $("[name=PxStats]").remove();
  $("[name=IsPageInFrame]").remove();
  $("[name=IIdValReal]").remove(); 
  $("[name=UserDeviceFingerprint]").remove(); 
  $("[name=CallerPageId]").remove(); 
}

function PxValidateBankAccount(name, index, countryName) {
  var ve = document.getElementsByName(name)[0];
  if (!ve) return;

  ve.value = ve.value.trim();
  var value = ve.value;

  for (var currentIndex = index; currentIndex; --currentIndex) {
    //if preceding field is filled out, then all of the fields should be filled out. only allow blank if all 4 inputs are blank
    var precedingElementIndex = getIndex(ve) - 1 - index + currentIndex;
    var precedingElement = ve.form[precedingElementIndex];
    if (precedingElement.value.length && value.length == 0 || precedingElement.value.length == 0 && value.length) {
      ve.setCustomValidity('Field must be filled out.');
      return;
    }
  }

  //atm. country checks are not too precise e.g. 32-37 would pass for NZ even though they are invalid. no branch validation either
  if (index == 0 && countryName == 'NZ' && value > 38)
    ve.setCustomValidity('Value must be between 0 and 38 for NZ banks, please try again.');
}

function PasteMultiBox(element, e, boxLengths, stripOutRegex, lengthRequirement) {
  var data = e.originalEvent.clipboardData.getData('Text').trim();

  if (stripOutRegex)
    data = data.replace(stripOutRegex, '');

  if (lengthRequirement && lengthRequirement != data.length) {
    //no way in javascript to get value of element before the paste (handler gets called afterwards) without a crap hack like setting a timeout. so just clear the element.
    element.value = '';
    alert('Length requirement was not met. Requirement: ' + lengthRequirement);
    return;
  }

  var i = 0;
  var elementIndex = getIndex(element);

  for (var index = elementIndex, substringStart = 0; i < boxLengths.length; ++i) {
    var individualBox = element.form[index + i];
    individualBox.value = data.substring(substringStart, substringStart + boxLengths[i]);
    substringStart += boxLengths[i];
  }
  //set focus 1 element past the last item in the multibox
  element.form[(elementIndex + i) % element.form.length].focus();
  element.form[(elementIndex + i) % element.form.length].select();
}


var XmlHttp = null;

function RemoveContent(id) {
  $("[name=" + id + "]").hide();
}

function InsertContent(id) {
  document.getElementById(id).style.display = "";
}

function GetXmlHttpObject() {
  try {
    // Firefox, Opera 8.0+, Safari
    xmlHttp = new XMLHttpRequest();
  } catch (e) {
    // IE
    try {
      xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
    } catch (e) {
      xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
    }
  }
  return xmlHttp;
}

$('.DpsSearchField').on('input', function() {
  if(this.value.length==0)
    $(this).trigger('SelectValueChanged'); //no item in the search <li> was clicked, however it was cleared so technically we should re-query
});

function ShowSearchUiItemList(responseObj) {
  var target = document.getElementById(responseObj.UiItemId);

  if (target == null)
    return;

  // remove elements if we've lost focus.
  target.onblur = function() {
    FinalizeSelectedListOption(this.id, this.value);
  };

  var divId = responseObj.UiItemId + '_' + "Popup";
  var existing = document.getElementById(divId);
  if (existing != null)
    document.body.removeChild(existing);

  var rootDiv = document.createElement('div');
  rootDiv.id = divId;
  rootDiv.setAttribute('class', 'DpsSearchPopupBox');

  PxAssignPosition(target, rootDiv, 0);

  var searchList = document.createElement('ul');
  searchList.id = responseObj.UiItemId + '_' + "list";
  searchList.setAttribute('class', 'DpsSearchList');
  searchList.setAttribute('role', 'listbox');

  var position = 0;
  var nextPosition = 0;

  $(responseObj.ListElements).each(
    function(index, elementValue) {
      var listElement = document.createElement('li');
      listElement.setAttribute('role', 'option');
      listElement.innerText = elementValue;
      listElement.textContent = elementValue.decodeHTML();

      listElement.id = responseObj.UiItemId;
      listElement.setAttribute('data-selected', 0);

      listElement.onmouseenter = function() {
        // clear all currently selected.
        var length = this.parentNode.children.length;
        for (var i = 0; i < length; ++i) {
          this.parentNode.children[i].setAttribute('data-selected', 0);
        }

        // set this element option to selected.
        searchOptionMouseHighlighted = true;
        this.setAttribute('data-selected', 1);
      };

      listElement.onmouseleave = function() {
        searchOptionMouseHighlighted = false;
        this.setAttribute('data-selected', 0);
      };
      // set callback when pressed.
      listElement.onclick = PxSearchOptionSelected; //tried to bind onkeydown here but could not
      searchList.appendChild(listElement);
    }
  );

  // if there was no search results, let them know.
  if (responseObj.ListElements.length == 0) {
    var listElement = document.createElement('li');
    listElement.setAttribute('role', 'option');
    listElement.innerText = searchOptionNoResultsFound;
    listElement.textContent = searchOptionNoResultsFound;
    listElement.id = responseObj.UiItemId;

    searchList.appendChild(listElement);
  }

  if (searchOptionPasteOn) {
    rootDiv.className += ' DpsHidden';
    searchOptionPasteOn = false;
  }

  rootDiv.appendChild(searchList);
  document.body.appendChild(rootDiv);
}

//this is done on a DataMap level now. Pretty sure can delete. Maybe wait till next release (so no risk of breaking CRM.)
function makeUnique(array) {
  return $.grep(array, function(el, index) {
    return index === $.inArray(el, array);
  });
}

function FilterDroplist(responseObj) {
  var target = $("#" + responseObj.UiItemId);
  var currentValue = target[0].value;

  target.empty();

  if (responseObj.ListElements && responseObj.ListElements.constructor === Array) {
    responseObj.ListElements.unshift(''); //i.e. push_front

    //not sure if makeUnique should be run based on an attribute being set. I think just always run it. 
    $(makeUnique(responseObj.ListElements)).each(
      function(index, elementValue) {
        if (elementValue != currentValue)
          target.append($("<option></option>").attr("value", elementValue.decodeHTML()).text(elementValue));
        else
          target.append($("<option selected></option>").attr("value", elementValue.decodeHTML()).text(elementValue));
      }
    );
  }
}

var referenceFilterCustomResponseHandlers =  {};

function FilterTextInput(responseObj) {
	var UiItem = document.getElementById(responseObj.UiItemId);
	//check if there's a custom handler for handling this ajax response object.
	if(referenceFilterCustomResponseHandlers[UiItem.id] != undefined) {
		referenceFilterCustomResponseHandlers[UiItem.id](responseObj, UiItem);
		return;
	}
	//there's no custom handler. So do the default, which is to grab the field with the same name as the DOM item
	var value = responseObj.Record[UiItem.getAttribute("name")]; //don't use .name as that won't work nicely for <span>s which we seem to use for readonly inputs
	
	if(UiItem.hasAttribute("readonly"))
		UiItem.innerHTML = value; //this is a <span>
	else
		UiItem.value = value;
}

function PxOnXmlHttpObjectStateChanged() {
  //basing this on: http://www.ajax-tutor.com/130/handle-response/
  if (this.readyState === 4 && this.status >= 200 && this.status < 300) {
    var responseObj = jQuery.parseJSON(this.responseText);

    if (responseObj.ActionType == "DoSearchFilter")
      ShowSearchUiItemList(responseObj);
    else if (responseObj.ActionType == "DoDroplistFilter")
      FilterDroplist(responseObj);
	else if(responseObj.ActionType == "DoTextInputFilter")
		FilterTextInput(responseObj);
    else if (responseObj.ActionType == "ValidateMerchant" || responseObj.ActionType == "DoTransaction")
      ApplePaySessionResponseReceived(responseObj, false);
	else
		console.log("Unknown actionType for AJAX response. " + responseObj.ActionType);
  }
  else if (this.status > 300) {
    var requestObj = jQuery.parseJSON(this.requestText);
    if (requestObj.ActionType == "ValidateMerchant" || requestObj.ActionType == "DoTransaction") {
      ApplePaySessionResponseReceived({}, true, requestObj);
    }
  }
}

// searchPanelInfo.searchButtonId is used to send the search request when clicking on "Apply to Default Sort" button and sortable column header
var searchPanelInfo = { searchButtonId: "" };

function PxInitialSearchButtonId(id) {
  searchPanelInfo.searchButtonId = id;
}

function PxSubmitSearch() {
  var SearchButton = $("#" + searchPanelInfo.searchButtonId);
  if (SearchButton && SearchButton.length) {
    SearchButton.click();
  }
}

function PxOnClickSortableColumnHeader(sortcolumnid, fieldname, sortorderfieldid) {
  var CurrentSortOrder = $("#" + sortcolumnid).data("CurrentSortOrder");
  var NextSortOrder = "";
  var FullSortOrderText = "";
  //sort order change sequence: "No order" -> ASC -> DESC -> "No order"
  if (!CurrentSortOrder) {
    NextSortOrder = "ASC";
  }
  if (CurrentSortOrder == "ASC") {
    NextSortOrder = "DESC";
  }
  if (CurrentSortOrder == "DESC") {
    FullSortOrderText = $("#" + sortorderfieldid).val();
    if (FullSortOrderText == fieldname.toUpperCase() + " DESC,") {
      NextSortOrder = "ASC";
    }
  }

  if (NextSortOrder) {
    PxChangeSortOrder(fieldname, NextSortOrder, sortorderfieldid);
  } else {
    PxRemoveSortOrder(fieldname, sortorderfieldid);
  }
  PxSubmitSearch();
}

function PxNormalizeSortOrderTex(sortorderfieldid) {
  // Add ASC if only column name is specified. <SortOrder>Username</SortOrder>. After invoking this function, the SortOrderText should be: USERNAME ASC, GROUPNAME DESC,
  var SortOrderText = $("#" + sortorderfieldid).val();
  if (!SortOrderText) // Defensive code. The sortorderfield should exist
    return;

  var SortOrderPairs = SortOrderText.split(","),
    SingleSortOrderPair;
  var LastSortPriority = 1;
  var i, TempItem;
  SortOrderText = "";
  for (i = 0; i < SortOrderPairs.length; ++i) {
    TempItem = SortOrderPairs[i].trim();
    if (!TempItem)
      break;
    ++LastSortPriority;
    SingleSortOrderPair = TempItem.split(" ");
    SortOrderText += SingleSortOrderPair[0].trim() + " ";
    if (SingleSortOrderPair.length == 2) {
      SortOrderText += SingleSortOrderPair[1].trim() + ", ";
    } else {
      SortOrderText += "ASC, ";
    }
  }
  SortOrderText.toUpperCase();

  var SortOrderFiledElement = $("#" + sortorderfieldid);
  SortOrderFiledElement.val(SortOrderText.trim());
  SortOrderFiledElement.data("LastSortPriority", LastSortPriority);
}

function PxRemoveSortOrder(fieldname, sortorderfieldid) {
  // Sort order text: RXDATE DESC,GROUPNAME ASC,
  var FieldSortReg = new RegExp(fieldname + " +(ASC|DESC),", "gi");
  var SortOrderElement = $("#" + sortorderfieldid);
  var UpdatedSortOrderText = SortOrderElement.val().replace(FieldSortReg, "");
  SortOrderElement.val(UpdatedSortOrderText);
}

function PxGetSortOrder(fieldname, sortorderfieldid) {
  var SortOrderText = $("#" + sortorderfieldid).val();
  if (!SortOrderText)
    return null;

  var SortOrderPairs = SortOrderText.split(","), TempItem, i;
  var FieldNameIndex, FieldNameUpperCase = fieldname.toUpperCase();
  var SortOrderText = "";
  for (i = 0; i < SortOrderPairs.length; ++i) {
    TempItem = SortOrderPairs[i].trim().toUpperCase();
    FieldNameIndex = TempItem.indexOf(FieldNameUpperCase);
    if (TempItem && FieldNameIndex >= 0) {
      // If only specify column name, sort by ASC. For example: <SortOrder>Username</SortOrder>
      SortOrderText = TempItem.substring(FieldNameIndex + fieldname.length).trim();
      return { SortOrderText: SortOrderText, OrderPriority: (i + 1) };
    }
  }
  return null;
}

function PxInitialSortableColumnHeader(sortcolumnid, fieldName, sortorderfieldid) {
  var SortOrderFiledElement = $("#" + sortorderfieldid);
  if (!SortOrderFiledElement || SortOrderFiledElement.length == 0) // Defensive code
    return;

  var SortOrderInfo = PxGetSortOrder(fieldName, sortorderfieldid);
  var CurrentSortOrder = "";
  // All the columns which not shows in sort order has same priority.
  var OrderPriority = SortOrderFiledElement.data("LastSortPriority");
  if (SortOrderInfo) {
    CurrentSortOrder = SortOrderInfo.SortOrderText;
    OrderPriority = SortOrderInfo.OrderPriority;
  }

  var SortableColumnHeaderElement = $("#" + sortcolumnid);
  SortableColumnHeaderElement.data("CurrentSortOrder", CurrentSortOrder);
  var ImageElement = $("<img></img>");
  var CssClass = "";
  if (!CurrentSortOrder) {
    ImageElement.attr("src", "/pxpay/images/Sort_Neutral.gif");
  } else if (CurrentSortOrder == "ASC") {
    ImageElement.attr("src", "/pxpay/images/Sort_ASC.gif");
    CssClass = "DpsSortImageSorted";
  } else if (CurrentSortOrder == "DESC") {
    ImageElement.attr("src", "/pxpay/images/Sort_DESC.gif");
    CssClass = "DpsSortImageSorted";
  }
  ImageElement.addClass(CssClass + " DpsSortPriority" + OrderPriority);
  SortableColumnHeaderElement.append(ImageElement);
}

function ConvertTxnDateTimeToLocalTime() {
  var dateTimeElem = document.getElementsByName('TxnDateTime')[0];
  if (dateTimeElem) {
    txntime = new Date(dateTimeElem.innerHTML);
    dateTimeElem.innerHTML = txntime.toLocaleString();
  }
}

function PxValidateDate(daySelect, monthSelect, yearSelect, allowFutureInput, futureOnly, localTime, localOriginalTime)
{
  //be careful: some of this stuff uses zero based index, other stuff is 1 based, other stuff is 0 or 1 based (e.g. depends on allowBlank value)
  //sometimes the month <select> has a blank <option> tag, so you could have no month input at all. This comes first.
  //Note: Date::getMonth() return month index 0-11, not the month but Date::getDate() return 1-31
  var hasBlankOption = monthSelect.options[0].text.length == 0;
  var monthIndex = monthSelect.selectedIndex - hasBlankOption;
  var invalidRangeLower = 28 + hasBlankOption;
  var invalidRangeHigher = 31 + hasBlankOption;
  //reset everything to valid
  for (var i = 0; i < daySelect.options.length; ++i)
    daySelect.options[i].disabled = false;

  for (var i = 0; i < monthSelect.options.length; ++i)
    monthSelect.options[i].disabled = false;

  var selectedYear = parseInt(yearSelect.options[yearSelect.selectedIndex].value);
  //if selected month is february
  if (monthIndex == 1) {
    var isLeap = selectedYear % 4 == 0;
    daySelect.selectedIndex = Math.min(daySelect.selectedIndex, 27 + hasBlankOption + isLeap);
    for (var i = invalidRangeLower + isLeap; i < invalidRangeHigher; ++i)
      daySelect.options[i].disabled = true;
  }

  //April, June, September, Novermber have 30 days
  if (monthIndex == 3 || monthIndex == 5 || monthIndex == 8 || monthIndex == 10) {
    daySelect.selectedIndex = Math.min(daySelect.selectedIndex, 29 + hasBlankOption);
    daySelect.options[30 + hasBlankOption].disabled = true;
  }

  // future only logic
  if (futureOnly) {
	  var currentTime = new Date(localTime); // need a PXMI3 logon session time, not local machine time
	  if (isNaN(currentTime)) {
		  currentTime = new Date();
	  }
	  var currentYear = currentTime.getFullYear();
	  //assert that if allowFutureInput is false, then the year limit ends at the current year
	  var currentMonth = currentTime.getMonth();

	  var originalTime = new Date(localOriginalTime);
	  if (isNaN(originalTime)) {
		  originalTime = new Date();
	  }

	  var originalYear = originalTime.getFullYear(); // 2018,2019,...
	  var originalMonth = originalTime.getMonth(); // 0-11
	  var originalDay = originalTime.getDate(); // 1-31

	  //disable past years except the 
	  for (var i = 0; i < yearSelect.options.length; ++i) {
		  var runningYear = parseInt(yearSelect.options[i].value);
		  if (runningYear < currentYear && runningYear != originalYear) {
			  yearSelect.options[i].disabled = true;
		  }
	  }
	  //future year or blank selected, all good, nothing to disable
	  if (selectedYear > currentYear || (hasBlankOption && yearSelect.selectedIndex == 0))
		  return;

	  //disable past months except the original month
	  var firstValidMonth = (selectedYear < currentYear) ? 12 : currentMonth;
	  var firstValidMonthIndex = firstValidMonth + (hasBlankOption ? 1 : 0);
	  var originalMonthIndex = (selectedYear == originalYear) ? originalMonth + (hasBlankOption ? 1 : 0) : -1;
	  for (var i = (hasBlankOption ? 1 : 0); i < firstValidMonthIndex; ++i) {
		  if (originalMonthIndex != i)
			  monthSelect.options[i].disabled = true;
	  }

	  // if disabled, use the first day that enabled
	  if (monthSelect.options[monthSelect.selectedIndex].disabled)
	  {
		  for (var i = (hasBlankOption ? 1 : 0); i < monthSelect.options.length; ++i) {
			  if (!monthSelect.options[i].disabled) {
				  monthSelect.selectedIndex = i;
				  break;
			  }
		  }
		  if (hasBlankOption && monthSelect.options[monthSelect.selectedIndex].disabled)
			  monthSelect.selectedIndex = 0;
	  }

	  monthIndex = monthSelect.selectedIndex - (hasBlankOption ? 1 : 0);

	  //future month, no need to disable date
	  if (selectedYear >= currentYear && monthIndex > currentMonth)
		  return;

	  //disable past days
	  var firstValidDay = (selectedYear < currentYear || (selectedYear == currentYear && monthIndex < currentMonth)) ? 32 : currentTime.getDate(); 
	  var firstInvalidDayIndex = hasBlankOption ? firstValidDay : firstValidDay - 1  ;
	  var originalDayIndex = (selectedYear == originalYear && monthIndex == originalMonth) ? originalDay - (hasBlankOption ? 0 : 1) : -1;
	  for (var i = (hasBlankOption ? 1 : 0); i < firstInvalidDayIndex; ++i)
	  {
		  if (originalDayIndex != i )
			daySelect.options[i].disabled = true;
	  }
	  // if disabled, use the first day that enabled
	  if (daySelect.options[daySelect.selectedIndex].disabled) {
		  for (var i = (hasBlankOption ? 1 : 0); i < daySelect.options.length; ++i) {
			  if (!daySelect.options[i].disabled) {
				  daySelect.selectedIndex = i;
				  break;
			  }
		  }
		  if (hasBlankOption && daySelect.options[daySelect.selectedIndex].disabled)
			  daySelect.selectedIndex = 0;
	  }
	  return;
  }

  //day of months checks done, now invalidate any inputs that are into the future.
  var currentTime = new Date();
  //add 1 day as selecting the current day actually selects midnight, which can mess up some filters - especially if the page has not got a filter for Time of day.
  currentTime.setDate(currentTime.getDate() + 1);
  var currentYear = currentTime.getFullYear();
  //assert that if allowFutureInput is false, then the year limit ends at the current year
  if (allowFutureInput || selectedYear != currentYear)
    return;

  var currentMonth = currentTime.getMonth();
  var firstInvalidMonthIndex = currentMonth + 1 + hasBlankOption;

  for (var i = firstInvalidMonthIndex; i < monthSelect.options.length; ++i)
    monthSelect.options[i].disabled = true;

  //if the current selectedIndex is invalid, then set it back to the first valid index e.g. if May is selected, but March is the first valid index, then set the control to March
  monthSelect.selectedIndex = Math.min(firstInvalidMonthIndex - 1, monthIndex + hasBlankOption);
  monthIndex = monthSelect.selectedIndex - hasBlankOption;

  if (monthIndex != currentMonth)
    return;

  var firstInvalidDayIndex = currentTime.getDate();

  for (var i = firstInvalidDayIndex; i < daySelect.options.length; ++i)
    daySelect.options[i].disabled = true;

  daySelect.selectedIndex = Math.min(firstInvalidDayIndex - 1, daySelect.selectedIndex);
}

function PxValidateDateRange(startDay, startMonth, startYear, endDay, endMonth, endYear, maxRange) {

  var startMonthHasBlankOption = ( startMonth.options[0].text.length == 0 ) ? 1 : 0;
  var intStartMonth = startMonth.selectedIndex - startMonthHasBlankOption;
  
   var startTime = new Date( parseInt(startYear.options[startYear.selectedIndex].value),
                             intStartMonth,
                             parseInt(startDay.options[startDay.selectedIndex].value) );
  
  var endMonthHasBlankOption = ( endMonth.options[0].text.length == 0 ) ? 1 : 0;
  var intEndMonth = endMonth.selectedIndex - endMonthHasBlankOption;
  
  var endTime = new Date( parseInt(endYear.options[endYear.selectedIndex].value),
                           intEndMonth,
                           parseInt(endDay.options[endDay.selectedIndex].value) );
    
  var oneDay = 24*60*60*1000; // hours*minutes*seconds*milliseconds
	
  var diffDays = Math.round((endTime.getTime() - startTime.getTime())/(oneDay));

  if ( diffDays < 0 || diffDays > maxRange ) 
  {
	   startDay.setCustomValidity("Please select valid date range, should be less than " + maxRange + " day" +(maxRange>1?"s":"") );
  }
  else 
  {
	   startDay.setCustomValidity("");
  }

}

// Save the sort order text to the hidden textbox.
function PxChangeSortOrder(fieldname, sortorder, sortorderfieldid) {
  var FieldSortReg = new RegExp(fieldname + " +(ASC|DESC),", "gi");
  var SortOrderElement = $("#" + sortorderfieldid);
  var OriginalSortOrderText = SortOrderElement.val();
  var UpdatedSortOrderText = OriginalSortOrderText;
  if (OriginalSortOrderText.match(FieldSortReg)) {
    UpdatedSortOrderText = OriginalSortOrderText.replace(FieldSortReg, fieldname + " " + sortorder + ", ");
  } else {
    UpdatedSortOrderText += fieldname + " " + sortorder + ",";
  }

  SortOrderElement.val(UpdatedSortOrderText);
}

function PxDoNavSubmit2(id, idval) {
  PxSubmitCurrentTab();

  PxAddStat({ "NavSubmit": { "Target": id, "Data": idval, "Time": PxTime() }});
  window.__statsSubmitted = true;

  var Target = document.getElementById('IId');
  if (Target == null)
    return;
  Target.value = id;
  Target = document.getElementById('IIdVal');
  if (Target == null)
    return;
  Target.value = idval;
  Target = document.getElementById('IIdValReal');
  if (Target == null)
    return;
  Target.value = 'ok';
}

function PxDoNavSubmit3(elem, id, idval) {
	PxSubmitCurrentTab();

	var Target = document.getElementById('IId');
	if (Target == null)
		return;
	Target.value = id;
	Target = document.getElementById('IIdVal');
	if (Target == null)
		return;
	Target.value = idval;
	document.getElementById('Form1').submit();
	if(isMicrosoft)
		$(elem).remove(); //don't ask...
}

function rowCountSelected() {
  document.getElementById('Form1').submit();
}

function processTableFooterRowInput(keyEvent) {
  if (keyEvent.keyCode == 13) {
    document.getElementById('Form1').submit();
    return false;
  }

  return NumbersOnly(keyEvent);
}

// Returns a newly instantiated hidden input field. It's ID and Name are set to the 'id'parameter. It's value is the 'value' parameter.
function PxCreateHiddenInput(id, value) {
  return $("<input />").attr("id", id).attr("name", id).attr("type", "hidden").attr("value", value);
}

// Inserts a new hidden input into the page container for every element in additionalValuesToSubmit. The input's id, name and value properties take their value from the name and value property present in each element.
function PxEmbedAdditionalValues(additionalValuesToSubmit) {
  $(additionalValuesToSubmit).each(
    function(index, element) {
      var input = $(".DpsPageContainer input[name='" + element.name + "']").get(0);
      if (input === undefined) {
        PxCreateHiddenInput(element.name, element.value).appendTo($(".DpsPageContainer"));
      } else {
        $(input).val(element.value);
      }
    });
}

// key status is stored and used when clicking on records.
var shiftKeyDown = false;
var altKeyDown = false;
var ctrlKeyDown = false;

$(document).mousedown(function(ev) {  
  if (ev.button != 1)
    return;

  var targ = ev.target || ev.srcElement;
  targ = $(targ);

  while (targ.length && targ.attr("onclickid") == null)
    targ = targ.parent();

  if (targ.length == 0)
    return;

  var closestTables = targ.closest("table");
  if (closestTables.length == 0)
    return;

  var closestTable = closestTables[0];
  var cacheShiftKey = shiftKeyDown;
  shiftKeyDown = true;
  PxOnClick(closestTable.id, targ.attr("onclickid"));
  //if onclick fails, set this back
  shiftKeyDown = cacheShiftKey;
});


window.onfocus = (function(ev) {
  // reset key status. when we move to a different tab, the state is kept the same, but may change when in a different window.
  shiftKeyDown = false;
  altKeyDown = false;
  var form = document.getElementById("Form1");
  if (form !== null) {
    form.removeAttribute("target");
  }
});

function PxOnClick(id, idval, additionalValuesToSubmit) {
  if (altKeyDown)
    return; // ignore clicks if alt key is held down. this helps with highlighting fields.

  var Target = document.getElementById('IId');
  if (Target == null)
    return;
  Target.value = id;
  Target = document.getElementById('IIdVal');
  if (Target == null)
    return;
  Target.value = idval;
  if(!Target.value || Target.value === "undefined") // when assigning Undefined to the Target.value, Target.value will become string "undefined", so need to leave this check
    return;
  
  if (additionalValuesToSubmit !== undefined)
    PxEmbedAdditionalValues(additionalValuesToSubmit);

  var form = document.getElementById('Form1');
  if (shiftKeyDown) {
    form.setAttribute('target', '_blank'); // add target _blank to the form to open in another tab/window.
  } else {
    form.removeAttribute('target'); // removes the attribute to make sure we open on the same tab/window.
  }

  PxSubmitCurrentTab();
  form.submit();
}

function PxResetSortOrder(id) {
  var selector = "#" + id;
  var def = $('#' + $(selector).attr('sort-table-id')).attr('default-sort-order');
  if (def) 
    $(selector).val(def);
  PxSubmitSearch();
}

function PxShowMenu(e) {
  PxHideAll();

  var Target = document.getElementById('IIdMenuShown');
  if (Target == null)
    return;
  Target.value = e;
  document.getElementById(e).style.display = 'block';
}

function PxToggleMenu(e) {
  var Target = document.getElementById('IIdMenuShown');
  if (Target == null)
    return;
  var collapsed = document.getElementById(e).style.display == 'block';
  Target.value = e;
  PxHideAll();
  if (collapsed)
    document.getElementById(e).style.display = 'none';
  else
    document.getElementById(e).style.display = 'block';
}

function PxHide(e) {
  document.getElementById(e).style.display = 'none';
}

var isNN = (navigator.appName.indexOf("Netscape") != -1);
var filter = (isNN) ? [0, 8, 9] : [0, 8, 9, 13, 16, 17, 18, 37, 38, 39, 40, 46]; //not a good name to give to a global variable...

function IsNumericKey(keycode) {
  // [48; 57] - nums from the main set of keys, [96; 105] - keypad nums
  return ((keycode >= 48) && (keycode <= 57)) || ((keycode >= 96) && (keycode <= 105));
}

function PxNumericKeyDown(event) {
  var keycode = event.keyCode;
  if (isNN && event.which != 0)
    keycode = event.which;
  // allow bckspc, home, end, tab, left, right, del
  var filter = [8, 9, 35, 36, 37, 39, 46, 13];
  if (containsElement(filter, keycode))
    return true;

  if (event.ctrlKey && keycode == 118) // ctrl-v, for Firefox
    return true;

  return (IsNumericKey(keycode) && !shiftKeyDown) || event.ctrlKey;
}

function PxSanitizeNumericValue(elem) {
  // Does not support negative values
  var currValue = elem.value;
  var index = 0;
  var needReplacement = false;
  var charCode = 0;
  for (index = currValue.length - 1; index >= 0; index--) {
    charCode = currValue.charCodeAt(index);
    if (IsNumericKey(charCode) == false) {
      currValue = currValue.substring(0, index);
      needReplacement = true;
    }
  }
  if (needReplacement == true)
    elem.value = currValue;
}


function NumbersOnly(e, allowMinus) {
  if (arguments.length == 1)
    allowMinus = false;

  var keyCode = e.keyCode;
  if (isNN && e.which != 0)
    keyCode = e.which;

  // allow bckspc, tab, left, right, del
  filter = [8, 9, 37, 39, 46, 13];
  if (((keyCode >= 48) && (keyCode <= 57)) || containsElement(filter, keyCode))
    return true;
  if (ctrlKeyDown && keyCode == 118) // ctrl-v, for Firefox
    return true;
  if (keyCode == 45 && allowMinus)
    return true;
  return false;
}

function NumbersOnlyWithMaxLength(input, e, allowMinus) {
  if (arguments.length == 1)
    allowMinus = false;

  var keyCode = e.keyCode;

  if (isNN && e.which != 0) {
    keyCode = e.which;
  }

  // allow bckspc, home, end, tab, left, right, del
  filter = [8, 9, 35, 36, 37, 39, 46, 13];
  if (containsElement(filter, keyCode))
    return true;

  if (ctrlKeyDown && keyCode == 118) // ctrl-v, for Firefox
    return true;

  if (keyCode == 45 && allowMinus)
    return true;
    
  var isNumericKey = (keyCode >= 48) && (keyCode <= 57);
  if (isNumericKey) {
    var maxlength = $(input).attr('maxlength');
    var valueLength = input.value.length + 1;
    return (valueLength <= maxlength);
  }
  return false;
}

function IsEnterKey(e) {
  var keyCode = (isNN) ? e.which : e.keyCode;
  if (keyCode == 13)
    return true;
  return false;
}

function getIndex(input) {
  var index = -1, i = 0, found = false;
  while (i < input.form.length && index == -1)
    if (input.form[i] == input)
      index = i;
    else
      i++;
  return index;
}

function NumbersOnlyPointTabs(input, e) {
  var keyCode = (isNN) ? e.which : e.keyCode;
  var notTabbed = AutoTabPoint(input, e);
  if (notTabbed && (((keyCode >= 48) && (keyCode <= 57)) || containsElement(filter, keyCode)))
    return true;
  return false;
}

function MaxLengthTabs(input, e) {
  //if tabbing via [Tab] key then keep value. If tabbing from maxlength, then clear it.
  if (e.keyCode == 9)
    return;

  var maxlength = $(input).attr('maxlength');
  if (maxlength == input.value.length) {
    var index = (getIndex(input) + 1) % input.form.length;
    input.form[index].focus();
    input.form[index].select();
  }
}

function containsElement(arr, ele) {
  var found = false, index = 0;
  while (!found && index < arr.length)
    if (arr[index] == ele)
      found = true;
    else
      index++;
  return found;
}

function AutoTabPoint(input, e) {
  var keyCode = (isNN) ? e.which : e.keyCode;
  if (keyCode == 46) {
    var index = (getIndex(input) + 1) % input.form.length;
    input.form[index].focus();
    input.form[index].select();
    return false;
  }
  return true;
}

function AutoTab(input, e) {
  var len = input.maxLength;
  var keyCode = (isNN) ? e.which : e.keyCode;

  if (input.value.length >= len && !containsElement(filter, keyCode)) {
    input.value = input.value.slice(0, len);
    var index = (getIndex(input) + 1) % input.form.length;
    input.form[index].focus();
    input.form[index].select();
  }

  return true;
}

function PxChangeAction(actionnam) { document.forms[0].action = actionnam; }

function PxShow(e) {
  var Target = document.getElementById(e);
  if (Target == null)
    return;
  Target.style.visibility = "visible";
  Target.style.display = "block";
}

function PxAskForConfirmation(confirmationText) {
  return confirm(confirmationText);
}

function PxAskForConfirmationInputAuditText(buttonElem, confirmationText, action) {
  if (action == null || action.length == 0)
    action = "Delete";

  var auditInput = $("[name='AuditReason']");
  if (auditInput.length == 0) {
    var formElem = $(buttonElem).closest('form');
    formElem.append("<input type='hidden' name='AuditReason'></input>");
  }

  var reason = prompt(confirmationText, "");
  if (reason != null && reason != "") {
    var maxLength = 255 - action.length - " Reason: ".length;

    if (reason.length > maxLength) {
      alert("Message is longer than " + maxLength + " characters.");
      return false;
    }

    $("[name='AuditReason']").val(reason);
    return true;
  } 
  return false;
}

/// a couple of global varaibles to make it a bit easier for the search results popup.
var searchOptionNoResultsFound = "No search results found";
var searchOptionMouseHighlighted = false;
var searchOptionCtrlOn = false;
var searchOptionPasteOn = false;

function PxAssignPosition(target, assigne, offset) {
  var x = target.offsetLeft;
  var y = target.offsetTop + target.offsetHeight;

  var parent = target;
  while (parent.offsetParent) {
    parent = parent.offsetParent;
    x += parent.offsetLeft;
    y += parent.offsetTop;
  }

  /* there is a bug this doesnt work in firefox always */
  assigne.style.left = (x + offset) + "px";
  assigne.style.top = (y + offset) + "px";
}

// Checks the dynamically set valid attribute to see if the value in the search is ok.
function PxCheckSearchValueValid(id) {
  var searchElement = document.getElementById(id);
  if (searchElement == null)
    return false; // doesn't exist, not valid.

  if (searchElement.hasAttribute('data-is-valid')) {
    var valid = Number(searchElement.getAttribute('data-is-valid')); //TODO I dont get this.
    searchElement.value = searchElement.value.trim();
    if (valid == 0 && searchElement.value.length != 0) {
      searchElement.setCustomValidity('Value you have entered is invalid, please try again.');
      return false;
    }
  }
  return true;
}


// Checks the selected list option to see if it's valid, before removing the popup.
function FinalizeSelectedListOption(target, value, EnableFocus) {
  var targetDiv = document.getElementById(target);

  var divId = target + '_' + "Popup";
  var existing = document.getElementById(divId);

  var listDivId = target + '_' + "list";
  var existingList = document.getElementById(listDivId);

  value = value.trim();
  if (existing != null && existingList != null && !searchOptionMouseHighlighted) {
    // make sure the input is valid by making sure it has the same value as a child.
    var inputValid = false;

    // look through the children and see if this value is valid.
    var length = existingList.children.length;
    for (var i = 0; i < length; ++i) {
      if ($(existingList.children[i]).text() === value) // make sure it's case sensitive.
        inputValid = true;
    }

    // store classes.
    var currentClasses = targetDiv.getAttribute('class');

    if (!inputValid && value.length != 0) {
      // not valid input, highlight error.
      currentClasses += ' DpsFieldError'; // add the error field.
      targetDiv.setAttribute('class', currentClasses);
      targetDiv.setAttribute('data-is-valid', 0); // make sure we set the valid variable for validation.
    } else {
      // remove the dps field error if it exists.
      var index = currentClasses.indexOf('DpsFieldError');
      if (index != -1) {
        var removedError = currentClasses.substring(0, index);
        targetDiv.setAttribute('class', removedError);
      }

      // make sure we set the valid variable for validation.
      targetDiv.setAttribute('data-is-valid', 1);
    }
    document.body.removeChild(existing);
  }

  if (EnableFocus)
    targetDiv.focus();
}


// Search option selected. Similar to PxOnSelect1, but checks to see if we're valid.
function PxSearchOptionSelected() {
	var target = document.getElementById(this.id);
	var targetName = target.getAttribute("name");
	if (targetName && window.pxapp)
		pxapp[targetName] = this.textContent;
	else
		target.value = this.textContent;

  searchOptionMouseHighlighted = false; // reset this option so we can remove the popup.
  FinalizeSelectedListOption(target.id, this.textContent, true); // focus the target element after selection
  $(target).trigger('SelectValueChanged');
}

// Catches on changed events from input fields.
var isDirty = false; //bad name for a global...

function PxInputChanged() {
	isDirty = true; // keep track if we've modified an input field. we can use during cancelation.

	// apply the DpsModifiedField style to this field.
	var currentClasses = this.getAttribute('class') || ""; // there are some input fileds without css class, for example, radibo button, hidden input
	if (currentClasses.indexOf("DpsModifiedField") < 0) {
		currentClasses += ' DpsModifiedField';
		this.setAttribute('class', currentClasses);
	}
}

$(':input').change(PxInputChanged);

// Keeps track of if we require cancel confirmation.
function PxCancelConfirmation() {
  if (isDirty)
    return confirm("You have unsaved settings.\r\nAre you sure you want to leave this page?");
  return true;
}

function PxGetClipboardData(clipboardEvent) {
  // In IE the window, rather than the event, contains the clipboardData property. Also unlike other browsers, IE doesn't identify the type of clipboard data via MIME.
  if (window.clipboardData && window.clipboardData.getData)
    return window.clipboardData.getData("Text");

  var currentClipboardEvent = clipboardEvent.originalEvent || clipboardEvent;
  if (currentClipboardEvent && currentClipboardEvent.clipboardData)
    return currentClipboardEvent.clipboardData.getData("text/plain");
  return "";
}

function PxNumericOnlyPasteHandler(clipboardEvent, maxLength, allowMinus) {
  if (arguments.length <= 2)
    allowMinus = false;

  var clipboardDataRaw = PxGetClipboardData(clipboardEvent);
  var prependMinus = allowMinus && clipboardDataRaw.length && clipboardDataRaw[0] == '-';
  var clipboardData = "";
  if (prependMinus)
    clipboardData += '-';

  clipboardData += clipboardDataRaw.replace(/\D/g, "");

  if (arguments.length == 1 || maxLength <= 0)
    maxLength = clipboardData.length;

  clipboardEvent.target.value = clipboardData.substr(0, maxLength);
  // Return false to prevent default handler
  return false;
}

// Shows the tab which the element is a child of. We also make sure to hide the currently active tab.
function PxShowTabWithElement(element) {
  if (element == null || element == undefined)
    return;
  var tab = $('#' + element.id).parentsUntil('div.tabberlive').last()[0];
  PxShowTab(tab);
}

function PxShowTab(tab) {
  if (tab && tab.parentElement && tab.parentElement.id) {
    var tabParent = tab.parentElement;
    $('#' + tabParent.id).find('li.tabberactive').first()[0].setAttribute('class', ''); // reset the current tab.
    $('#' + tabParent.id).find('div.tabbertab ').not('div.tabbertabhide').first()[0].setAttribute('class', 'tabbertab tabbertabhide'); // hide the current active tab.
    tab.setAttribute('class', 'tabbertab '); // show the new active tab.
    $('#' + tabParent.id).find("a[title='" + tab.children[0].children[0].name + "']").first()[0].parentElement.setAttribute('class', 'tabberactive'); // highlight tab.
  }
}

// Checks if there's any invalid elements, then displays the element's tab.
function PxShowInvalidTab() {
  var invalidElement = $('#Form1').find('input:invalid,select:invalid,textarea:invalid').first()[0];
  PxShowTabWithElement(invalidElement);
}

function PxCheckIsItemSelected(id) {
  var element = $("#" + id);
  var value = element.val();
  element.get(0).setCustomValidity("");
  if (value === null || value === undefined)
    element.get(0).setCustomValidity("Please select an item");
}

function SessionTimeoutWarning(time) {
  setTimeout(function() {
    alert("Sorry, your session is now expiring...");
    location.reload();
  }, time);
}

function PxValidateBlankInput() {
  var value = $(this).val();
  if (value.length > 0 && value.trim().length == 0) {
    $(this).setCustomValidity("Value must be not empty, please try again.");
    return false;
  }
  return true;
}

function PxFieldInvalidStyle(panelName) {
  var defaultValidator = function(){return true;};
  var inputValidators = { input: PxValidateBlankInput, select: defaultValidator };
  var currentInputType;
  for (currentInputType in inputValidators){
    $("#Form1 " + currentInputType).each(function() {
      $(this).removeClass('DpsFieldError');
      $(this).unbind('blur');
    });

    $("#Form1 #" + panelName + " " + currentInputType).each(function () {
      PxFieldInvalidStyleHandler.call(this, panelName, inputValidators[currentInputType]);
    });
  }
}

function PxCheckBlankSpace(panelId) {
  var isValid = true;
  $("#Form1 #" + panelId + " input").each(function () {
    if (!PxValidateBlankInput.call(this, panelId))
      isValid = false;
  });
  return isValid;
}

function PxValidateForm1() {
    var isCaptchaEnabledDefined = typeof IsCaptchaEnabled != "undefined";
    var isCaptchaEnabledResult = isCaptchaEnabledDefined && IsCaptchaEnabled();
    var captchaInputResult = true;
	if (isCaptchaEnabledDefined)
        captchaInputResult = CheckCaptchaInput();
    if (PxAddStat) PxAddStat({ "validate1": { "Data": "captchaFuncDefined:" + isCaptchaEnabledDefined + " enabled:" + isCaptchaEnabledResult + " check:" + captchaInputResult, "Time": PxTime() }});

    if (isCaptchaEnabledDefined && !captchaInputResult)
        return false;
	
	if(!document.getElementById('Form1').checkValidity())
		return false;
	
	return true;
}

function PxValidateCaptcha(name) {
  var panel = $("[name=" + name + "]");
  var form = $("#Form1");
  form.attr("_CaptchaCorrect", true);
  var result = true;
  var captchaPanel = $("#CaptchaContainer", panel[0]);
  if (captchaPanel.length > 0 && captchaPanel[0].children.length > 0)
    result = CheckCaptchaInput();
  form.attr("_CaptchaCorrect", result);
}

function PxIsCaptchaIgnoredSubmit() {
	// clear captcha validation error when click cancel.
	var iid = $("#IId").val();
	var iidElement = document.getElementById(iid);
	return iidElement && iidElement.formNoValidate;
}

function PxIsCaptchaCorrect() {
  var form = $("#Form1");

	if (PxIsCaptchaIgnoredSubmit())
	form.attr("_CaptchaCorrect", true);

  var isCaptchaCorrect = form.attr("_CaptchaCorrect");
  return isCaptchaCorrect != "false";
}

function PxCheckIsPageInframe() {
  var isInframe = 0;
  try {
    isInframe = ((window.self !== window.top) ? 1 : 0);
  } catch (e) {
    isInframe = 1;
  }

  var isInframeElement = $("[name=IsPageInFrame]");
  isInframeElement.val(isInframe);
}

function PxFieldInvalidStyleHandler(panelName, additionalValidate) {
  if (!this.checkValidity() || !additionalValidate.call(this)) {
    $(this).addClass('DpsFieldError');
  } else {
    $(this).removeClass('DpsFieldError');
  }

  $(this).unbind('blur');
  $(this).on('blur', function() {
    PxValidate(panelName);
    PxFieldInvalidStyleHandler.call(this, panelName, additionalValidate);
  }.bind(this));
}

function PxSetCurrentTab() {
  if ($('#CurrentTab').length == 0)
    return;

  var tabName = $('#CurrentTab').val().trim();
  // E.g. PageConfig_8934_PxPayCustomHosted.txt: 'Copy To' button should use the first tab
  if ($('input[name=DefaultTab]').length > 0)
    tabName = $('input[name=DefaultTab]').val();

  var tab = $('#tab1 a.DpsTab[name=\'' + tabName + '\']').parent().parent();
  // Restore the current active tab if it's the same record
  if (tab.length > 0)
    PxShowTab(tab[0]);
}

function PxSubmitCurrentTab() {
  if ($('#CurrentTab').length == 0)
    return;

  var tabName = $('#tab1').find('li.tabberactive a').first().text().trim();
  if (tabName != '')
    $('#CurrentTab').val(tabName);
}

function PxResetForm() {
  $('input.DpsModifiedField').removeClass('DpsModifiedField');
  isDirty = false;
}

function PxClearForm() {
  $('input.DpsModifiedField').val("");
  $('input.DpsModifiedField').removeClass('DpsModifiedField');
  isDirty = false;
}

function PxClickAndDisable(e) {
  var id = e.target.id;
  var selector = "#" + id;
  if ($(selector).data("clicked"))
    e.preventDefault();
  $(selector).data("clicked", true);
}

function PxCheckForAndHandleSubmission($event) {
  if ($event.which !== 13)
    return true;

  // Figure out which panel contains the signalling element.
  var $parentPanel = $($event.target).parents("div.DpsUiPanel");

  // Find the first available submit contained by the panel.
  var $submitButton = $parentPanel.find(":submit:not(.IgnoreMeOnEnterKey)").first();
  // Propogate the event if we couldn't find a suitable button.
  if ($submitButton.length === 0) {
    PxAddStat({ "EnterPressed": { "Target": $event.target.name, "Data": "undefined", "Time": PxTime() }});
    return true;
  }

  PxAddStat({ "EnterPressed": { "Target": $event.target.name, "Data": $submitButton[0].name, "Time": PxTime() }});
  //when tabs are present, there appears to be some extra logic that causes annoying problems. So remove the IgnoreMeOnEnterKey buttons.
  var tabsHack = $parentPanel.find(".IgnoreMeOnEnterKey");
  tabsHack.remove();
  $submitButton.click();

  // Don't propogate the event. We don't want the the form trying to submit itself...
  return false;
}

$(document).ready(function() {
  if (!String.prototype.endsWith) {
    String.prototype.endsWith = function(pattern) {
      var d = this.length - pattern.length;
      return d >= 0 && this.lastIndexOf(pattern) === d;
    };
  }

  if (window.location.href.endsWith('from=pxmi2')) {
      $('#Form1').prepend('<div id="welcome" class="DpsWarningBanner">' +
      'Welcome to the new Payline. See more about the new Payline, please ' +
      '<a href="http://www.paymentexpress.com/Products/Payline" target="_blank">download the manual here.</a>' +
      '<a id="WelcomeClose" href="">Close</a>' +
      '</div>').on('click', '#welcome #WelcomeClose', function() {
      $('#welcome').hide();
      return false;
    });

    $('#welcome').slideDown('slow');
  }

  if (typeof window.PxNakedPageButtonClick === "undefined") return;
  var es = document.getElementsByTagName("button");
  for (var i = 0; i < es.length; ++i)
    $(es[i]).unbind('click', PxNakedPageButtonClick);
});

function PxAddStat(value) {
  // PxStats currently exist only on PxPay page. On all the other cases just the call.
  var pxstat = document.getElementsByName("PxStats")[0];
  if (typeof pxstat === "undefined")
    return;
  __pxStats.push(value);
  pxstat.value = JSON.stringify(__pxStats);
}

function PxAddStatWithTime(value) {
  var obj = {};
  obj[value] = { "Time": PxTime() };
  PxAddStat(obj);
}

function PxAddFP() {
  var a = document.getElementsByName("UserDeviceFingerprint")[0];
  if ("undefined" != typeof a && window.pxClientFingerprint)
    a.value = window.pxClientFingerprint;
}

function PxQuickSearchPaddingZero(valueElement) {
  var val = '0000000000000000' + valueElement.value;
  var length = 16;
  if (valueElement.hasAttribute('maxLength')) {
    var maxLength = valueElement.getAttribute('maxlength');
    if (maxLength < length)
      length = maxLength;
  }
  valueElement.value = val.slice(-1 * length);
}

function PxIsQuickSearchInputValid(searchElement) {
  if (searchElement.value.trim() == '')
    return false;
  $("form#Form1 :input").each(function () {
    this.removeAttribute('required');
    this.setCustomValidity('');
  });

  searchElement.setCustomValidity('');
  if (!searchElement.checkValidity())
    return false;
  return true;
}

function PxQuickSearchOnSubmit(enablePaddingZero, inputId, submitButtonId) {
  var searchElement = document.getElementById(inputId);
  if (searchElement == null)
    return false; 

  if (searchElement.value.trim() == '')
    return false;

  if (enablePaddingZero)
    PxQuickSearchPaddingZero(searchElement);

  if (PxIsQuickSearchInputValid(searchElement))
  {
	PxDoNavSubmit2(inputId, searchElement.value);
  }
  return true;
}

function PxSetIconOnElement(target, url, padLeft, size, repeatBackground) {
	var elementStyle = target.style;
	
	if(url == null) 
		url = '';
	
	if(padLeft == null)
		padLeft = "0px";
	
	if(size == null)
		size = "0px 0px";
	
	if(repeatBackground == null)
		repeatBackground = true;
	
	elementStyle.background = "url('" + url + "')";
	elementStyle.paddingLeft = padLeft;
	elementStyle.backgroundSize = size;
	elementStyle.backgroundRepeat = repeatBackground ? "repeat" : "no-repeat";
}

function PxSetCardtypeLogo(LogoUrl) {
    if (document.getElementById("CardTypeLogo")) {
        document.getElementById("CardTypeLogo").src = LogoUrl;
    }
    else {
        var icon = document.createElement("img");
        icon.id = "CardTypeLogo";
        icon.style = "position:relative!important;width:25px;border:none;margin-left:-28px;z-index:999;";
        icon.setAttribute("src", LogoUrl);
        $("[name='CardNumber']")[0].parentNode.insertBefore(icon, $("[name='CardNumber']")[0].nextSibling);
    }
}

function PxOnCurrencyChange(selectedCurrencyTxt, symbolElem, fractionElem, currencyLookupTable)
{
    var currencyObj = currencyLookupTable && currencyLookupTable[selectedCurrencyTxt];
    if (currencyObj)
    {
        if (symbolElem)
            symbolElem.innerHTML = currencyObj.sym || "";
        if (fractionElem)
		{
			var originalLength = fractionElem.maxLength;
            var exp = currencyObj.exp;
            fractionElem.disabled = !exp;
            var expValueStr = "";
            if (exp) {
              fractionElem.maxLength = exp;
              while(expValueStr.length < exp)
                  expValueStr = expValueStr.concat("0");
			}

            fractionElem.value = expValueStr;
			fractionElem.placeholder = expValueStr;

			if (exp && originalLength) {
				// use getComputedStyle as offsetWidth contain border
				var newWidth = window.getComputedStyle(fractionElem).width.match(/\d+/) / originalLength * exp;
				if (newWidth) {
					fractionElem.style = "width:" + newWidth + "px!important;";
				} else {
					fractionElem.style = "width:" + exp * 10 + "px!important;"; // just in case it getComputedStyle is not supported < IE8
				}
			}
        }
    }
}


function PxOnFileChange(Id, maxFileSize) {
    if (maxFileSize == 0)
        return;

    var messagefield = document.getElementById('message_' + Id);
	messagefield.innerHTML = "";
    var fileelement = document.getElementById(Id);
    var size = fileelement.files[0].size;
    if (size > maxFileSize) {
        var messaging = 'upload file size= ' + size + ' is larger than MaxFileSize= ' + maxFileSize;
		messagefield.innerHTML = messaging;
        fileelement.setCustomValidity(messaging);
    }
    else {
        fileelement.setCustomValidity("");
    }
    
}

function PxOnInvalid(element, displayName) {
    element.setCustomValidity($(element).prop("validationMessage").replace("Please fill out this field.", "Please enter a valid " + displayName + "."));
}

document.onkeydown = (function(ev) {
  var key;
  if (window.event) {
    key = window.event.keyCode;
    shiftKeyDown = window.event.shiftKey ? true : false;
    altKeyDown = window.event.altKey ? true : false;
    ctrlKeyDown = window.event.ctrlKey ? true : false;
  } else {
    key = ev.which;
    shiftKeyDown = ev.shiftKey ? true : false;
    altKeyDown = ev.altKey ? true : false;
    ctrlKeyDown = ev.ctrlKey ? true : false;
  }
});

document.onkeyup = (function(ev) {
  var key;
  if (window.event) {
    key = window.event.keyCode;
    shiftKeyDown = window.event.shiftKey ? true : false;
    altKeyDown = window.event.altKey ? true : false;
  } else {
    key = ev.which;
    shiftKeyDown = ev.shiftKey ? true : false;
    altKeyDown = ev.altKey ? true : false;
  }
});
if (!window.__pxStats) window.__pxStats = [];
if (!window.PxTime) window.PxTime = function () { return "" + new Date().getTime(); };

PxAddStatWithTime("JSLoaded");
PxAddStat({ "UserAgent": { "Data": navigator.userAgent, "Time": PxTime() }});

function getAllSelectors() {
	var ret = [];
	for (var i = 0; i < document.styleSheets.length; i++) {
		var rules = document.styleSheets[i].rules || document.styleSheets[i].cssRules;
		for (var x in rules) {
			if (typeof rules[x].selectorText == 'string') ret.push(rules[x].selectorText);
		}
	}
	return ret;
}

function selectorExists(selector) {
	var selectors = getAllSelectors();
	for (var i = 0; i < selectors.length; i++) {
		if (selectors[i] == selector) return true;
	}
	return false;
}

function PxShowOpaqueScreen() {
	// determin full size of the opaque screen, seems body can be smaller than window and vice versa.
	var width = $(window).width();
	var height = $(window).height();

	var body = $('body');
	if (body.width() > width)
		width = body.width();
	if (body.height() > height)
		height = body.height();

	var divHtml = $("<div class = 'DpsSubmitOverlay' style = 'background-color: rgba(255, 255, 255, 255); opacity:0.8; width:100% height:100% left: 0; top: 0; position: absolute;'></div>");
	divHtml.width(width);
	divHtml.height(height);
	$("body").prepend(divHtml);
}


function PxHideOpaqueScreen() {
	var divToRemove = $(".DpsSubmitOverlay");
	divToRemove.remove();
}