File: D:/HostingSpaces/SBogers10/werken-bij-stafa.komma.pro/resources/js/kms/attributes/password.js
class PasswordController {
/**
* Validates password fields.
* The password fields values wil be imploded with a pipe symbol
*
* @param passwordInputSelector The css selector for selecting the password input fields
* @param wrapperSelector The css selector for the wrapper div / element that wraps the first and second input fields along the validation helper
* @param passwordDontMatchErrorText string
* @param saveButtonId The id of a save button that needs to be disabled when the passwords are not valid
* @param minPasswordLength int
* @param wrapperHasTitleAttributeAndErrorClass Must be set to true if the wrapper (selected with the wrapper selector) has the ability to have an error class and title attribute for displaying an error
*/
constructor(passwordInputSelector, wrapperSelector, passwordDontMatchErrorText, saveButtonId, minPasswordLength = 6, wrapperHasTitleAttributeAndErrorClass = false) {
this.wrapperHasTitleAttributeAndErrorClass = wrapperHasTitleAttributeAndErrorClass;
this.wrapper = document.querySelector(wrapperSelector);
this.saveButton = document.querySelector("#"+saveButtonId);
this.csrfTokenContainer = document.querySelector('meta[name="csrf-token"]');
if(!this.csrfTokenContainer) {
console.error('PasswordController: could not find the csrf token container. Password controller does not work');
return;
}
this.mailButtonWrapper = this.wrapper.querySelector('.js_password_mail');
if(!this.mailButtonWrapper) {
console.error('PasswordController: could not find the wrapper that holds a button to mail the user instructions on how to set his password. Password controller does not work');
return;
}
this.mailButton = this.mailButtonWrapper.querySelector('button');
if(!this.mailButton || !("url" in this.mailButton.dataset) || !("userId" in this.mailButton.dataset)) {
console.error('PasswordController: Could not find a button within the mail button wrapper, that has the data attributes url and user-id. Password controller does not work.');
return;
}
this.mailButtonUrl = this.mailButton.dataset.url;
this.mailButtonUserId = this.mailButton.dataset.userId;
this.mailConfirmation = this.mailButtonWrapper.querySelector('p.js_confirmation');
if(!this.mailConfirmation) {
console.error('PasswordController: Could not find a paragraph with class js_confirmation. Password controller does not work.');
return;
}
this.mailFailMessage = this.mailButtonWrapper.querySelector('p.js_fail');
if(!this.mailFailMessage) {
console.error('PasswordController: Could not find a paragraph with class js_fail. Password controller does not work.');
return;
}
this.firstPasswordInput = this.wrapper.querySelector('input[name=' + passwordInputSelector + '-1]');
this.secondPasswordInput = this.wrapper.querySelector('input[name='+passwordInputSelector+ '-2]');
this.realPasswordInput = this.wrapper.querySelector('input[name='+passwordInputSelector+ ']');
this.validationMessageWrapperSelector = '.validationHelper';
this.validationMessageWrapper = this.wrapper.querySelector(this.validationMessageWrapperSelector);
this.minPasswordLength = minPasswordLength;
//Used for validation
this.letter = this.validationMessageWrapper.querySelector(".letter");
this.capital = this.validationMessageWrapper.querySelector(".capital");
this.number = this.validationMessageWrapper.querySelector(".number");
this.length = this.validationMessageWrapper.querySelector(".length");
this.match = this.validationMessageWrapper.querySelector(".match");
// this.noPipe = document.querySelector(this.validationMessageWrapperSelector + " .noPipe");
if(!this.mailButtonUserId) this.mailButtonWrapper.classList.add('hidden'); //No user id means that we have a new non existing user. So we cannot mail the user yet.
this.mailButtonClicked = this.mailButtonClicked.bind(this); //Needed to make sure that "this" inside the method always refers to the password controller.
this.activateListeners(true);
}
/**
* Activates listening for keyup events on the password fields to that the passwordChanged method is triggered
*
* @param state
*/
activateListeners(state) {
let self = this;
let validationHelper = this.validationMessageWrapper;
if (state) {
this.firstPasswordInput.addEventListener('keyup',
self.debounce(function () {
self.passwordChanged()
}, 100)
);
this.secondPasswordInput.addEventListener('keyup',
self.debounce(function () {
self.passwordChanged()
}, 100)
);
this.firstPasswordInput.addEventListener('focus', function () {
if (!validationHelper.classList.contains('active')) validationHelper.classList.add('active')
});
this.secondPasswordInput.addEventListener('focus', function () {
if (!validationHelper.classList.contains('active')) validationHelper.classList.add('active')
});
this.firstPasswordInput.addEventListener('blur', function () {
if (validationHelper.classList.contains('active')) validationHelper.classList.remove('active')
});
this.secondPasswordInput.addEventListener('blur', function () {
if (validationHelper.classList.contains('active')) validationHelper.classList.remove('active')
});
this.mailButton.addEventListener('click', this.mailButtonClicked);
} else {
this.firstPasswordInput.removeEventListener('keyup', self.debounce);
this.secondPasswordInput.removeEventListener('keyup', self.debounce);
//TODO, these remove listeners won't work. since they need a reference to the original function. the original functions also need to be bound properly when adding them
this.firstPasswordInput.removeEventListener('focus');
this.secondPasswordInput.removeEventListener('focus');
this.firstPasswordInput.removeEventListener('blur');
this.secondPasswordInput.removeEventListener('blur');
this.mailButton.addEventListener('click', this.mailButtonClicked);
}
}
/**
* Triggered when the mail set password button was clicked.
*
* @var {MouseEvent} mouseEvent
*/
mailButtonClicked(mouseEvent)
{
let self = this;
mouseEvent.preventDefault();
let request = new XMLHttpRequest();
request.open("POST", this.mailButtonUrl, true);
//Send the proper header information along with the request
request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
request.setRequestHeader("X-CSRF-TOKEN", this.csrfTokenContainer.getAttribute('content'));
request.setRequestHeader("X-Requested-With", 'XMLHttpRequest');
request.onreadystatechange = (function(passwordController, originalRequest) {
return function() {
if(this.readyState === XMLHttpRequest.DONE) {
if ((this.status === 200) || this.status === 204) {
passwordController.mailSend();
} else {
passwordController.mailError();
}
}
};
}(self, request));
this.mailButton.classList.add('hidden');
request.send("user_id="+this.mailButtonUserId);
}
/**
* Triggered when the user could not be mailed the set password mail for some reason.
*/
mailError()
{
this.mailFailMessage.classList.remove('hidden');
}
/**
* Triggered when the user did get a set password mail.
*/
mailSend()
{
this.mailConfirmation.classList.remove('hidden');
}
/**
* Triggered when one of the password fields are changed and if the listeners for those fields are active
*/
passwordChanged() {
console.log('password changed');
let value1 = this.firstPasswordInput.value;
let value2 = this.secondPasswordInput.value;
let valid = (this.validate(value1, value2));
console.log(valid);
if(valid) this.realPasswordInput.value = value2;
else this.realPasswordInput.value = '';
this.enableValidMessage(valid);
this.enableSaveButton(valid);
if(value1 === '' && value2 === '') {
this.enableSaveButton(true);
}
this.removeWrapperError();
}
enableValidMessage(enable)
{
let validationHelper = this.validationMessageWrapper;
if(enable) {
if(!validationHelper.classList.contains('valid')) validationHelper.classList.add('valid');
} else {
if(validationHelper.classList.contains('valid')) validationHelper.classList.remove('valid');
}
}
enableSaveButton(enable)
{
console.log(enable);
if(enable) {
if(this.saveButton.classList.contains('disabled')) this.saveButton.classList.remove('disabled');
} else {
if(!this.saveButton.classList.contains('disabled')) this.saveButton.classList.add('disabled');
}
}
/**
* Removes the error that may be set on the wrapper
*/
removeWrapperError()
{
if(!this.wrapperHasTitleAttributeAndErrorClass) return;
if(this.wrapper.hasAttribute('title')) this.wrapper.setAttribute('title', '');
if(this.wrapper.classList.contains('error')) this.wrapper.classList.remove('error');
}
/**
* Validate two values and return true or false if respectively valid or not
*
* @param value1
* @param value2
* @returns {boolean}
*/
validate(value1, value2)
{
let valid = true;
// Validate lowercase letters
let lowerCaseLetters = /[a-z]/g;
if(value1.match(lowerCaseLetters)) {
this.letter.classList.remove("invalid");
this.letter.classList.add("valid");
} else {
this.letter.classList.remove("valid");
this.letter.classList.add("invalid");
valid = false;
}
// Validate capital letters
let upperCaseLetters = /[A-Z]/g;
if(value1.match(upperCaseLetters)) {
this.capital.classList.remove("invalid");
this.capital.classList.add("valid");
} else {
this.capital.classList.remove("valid");
this.capital.classList.add("invalid");
valid = false;
}
// Validate numbers
let numbers = /[0-9]/g;
if(value1.match(numbers)) {
this.number.classList.remove("invalid");
this.number.classList.add("valid");
} else {
this.number.classList.remove("valid");
this.number.classList.add("invalid");
valid = false;
}
// Validate numbers
// let pipe = /\|/g;
// if(value1.match(pipe)) {
// this.noPipe.classList.remove("valid");
// this.noPipe.classList.add("invalid");
// valid = false;
// } else {
// this.noPipe.classList.remove("invalid");
// this.noPipe.classList.add("valid");
// }
// Validate length
if(value1.length >= this.minPasswordLength) {
this.length.classList.remove("invalid");
this.length.classList.add("valid");
} else {
this.length.classList.remove("valid");
this.length.classList.add("invalid");
valid = false;
}
// Validate password match
if(value1 === value2 && (value1 !== '' || value2 !== '')) {
this.match.classList.remove("invalid");
this.match.classList.add("valid");
} else {
this.match.classList.remove("valid");
this.match.classList.add("invalid");
valid = false;
}
return valid
}
/**
* Triggers the function after the wait time (milliseconds) has expired and only
* if the function is not triggered again before the wait time expired.
*
* @param func
* @param wait
* @param immediate
* @returns {Function}
*/
debounce(func, wait, immediate) {
let timeout;
return function() {
let context = this, args = arguments;
let later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
let callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
}