declare let $: any; function createFormInputItem(input) { const attributes = input.attributes, formInputItem: any = { // input element's value is special, value property and value attribute not sync value: input.value, hasValue() { return ( this.value !== undefined && this.value !== null && this.value !== '' ); }, hasDisplay() { return ( this.display !== undefined && this.display !== null && this.display !== '' ); }, }; for (let i = 0; i < attributes.length; i++) { if (attributes[i].name != 'value') { formInputItem[attributes[i].name] = attributes[i].value; } } if (input.dataset && input.dataset.display) { formInputItem.display = input.dataset.display; } return formInputItem; } function getMultiValues(select) { let option, values = []; for (let i = 0; i < select.options.length; i++) { option = select.options[i]; if (option.selected) { values.push(createFormInputItem(option)); } } return values; } /** * @param: A string selector of the parent form element * @return: A dictionary object representing a form. Transforming the form from html element into a dictionary object, * which contains functions created by createFormInputItem() */ export function formToObj(selector) { let i, form: any = {}, formInputItem: any, selectedOption, input: HTMLInputElement, select: HTMLSelectElement, textarea: HTMLTextAreaElement, inputs: NodeListOf = >( document.querySelectorAll(selector + ' input') ), selects: NodeListOf = < NodeListOf >document.querySelectorAll(selector + ' select'), textareas: NodeListOf = < NodeListOf >document.querySelectorAll(selector + ' textarea'); for (i = 0; i < inputs.length; i++) { input = inputs[i]; if ('checkbox' === input.type) { if (input.checked) { let formInput = form[input.name]; if (!formInput) { formInput = []; form[input.name] = formInput; } formInputItem = createFormInputItem(input); formInput.push(formInputItem); } } else if ('radio' === input.type) { if (input.checked) { form[input.name] = createFormInputItem(input); } } else { let formInput = form[input.name]; if (!formInput) { form[input.name] = createFormInputItem(input); } else { if (!Array.isArray(formInput)) { formInput = [formInput]; } formInputItem = createFormInputItem(input); formInput.push(formInputItem); form[input.name] = formInput; } } } for (i = 0; i < selects.length; i++) { select = selects[i]; if (select.multiple) { form[select.name] = getMultiValues(select); } else { if (select.selectedIndex > -1) { selectedOption = select.options[select.selectedIndex]; form[select.name] = createFormInputItem(selectedOption); } } } for (i = 0; i < textareas.length; i++) { textarea = textareas[i]; form[textarea.name] = createFormInputItem(textarea); } return form; } /** * tranform form object into a simple javascript object with key value pairs * @param formObj */ export function formObjToObj(formObj, removeNoValueFields = true) { const obj = {}; for (const p in formObj) { if (Object.prototype.hasOwnProperty.call(formObj, p)) { const v = formObj[p]; if (Array.isArray(v)) { const parameterArray = []; for (let i = 0; i < v.length; i++) { if ( (v[i].hasValue && v[i].hasValue()) || !removeNoValueFields ) { parameterArray.push(v[i].value); } } obj[p] = parameterArray; } else { if ((v.hasValue && v.hasValue()) || !removeNoValueFields) { obj[p] = v.value; } } } } return obj; } export function updateFormInputs(formObj, formSelector = '#filters-form') { let name, inputSelector, input; for (name in formObj) { inputSelector = `${formSelector} [name=${name}]`; input = document.querySelector(inputSelector); switch (input.type) { case 'text': case 'hidden': input.value = formObj[name]; break; default: break; } } } /** * Adapt the element's height to its scroll height. * @param selector */ export function adaptToScrollHeight(selector) { const elmt = document.querySelector(selector); if (elmt) { elmt.style.height = elmt.scrollHeight + 'px'; } } export function textareaAutoHeight(selector) { $(selector).on('focus blur keyup', function () { adaptToScrollHeight(selector); }); } /** * Takes in the value of a delimited input * and splits the value into an array based on the delimiter * and then cleans the array by trimming whitespace from each item * and subsequently removes empty items. * * @param inputValue * @param delimiter */ export function getCleanDelimitedFormInputArray(inputValue, delimiter = ';') { return inputValue .split(delimiter) .map((item) => item.trim()) .filter((item) => item); } /** * takes the name and value of the html input element and construct a string representing the parameters used in url get request * @param elementList: a list of HTMLInputElement * @return: a string, example "field_1_name=value_1&field_2_name=value_2" */ export function elementsToURIParams(elementList: HTMLInputElement[]) { let uriParamString, paramPair, uriParamsArray = []; for (const item of elementList) { paramPair = item.name + '=' + item.value; uriParamsArray.push(paramPair); } uriParamString = encodeURI(uriParamsArray.join('&')); return uriParamString; } /** * count how many characters are left for a field that has max limit of input characters * display the count number in red if passing the limit * @param maxLimit: integer * @param targetFieldId: string, id of the input box * @param notificationId: string, id of the notification message div * * example of using it with form validation error messages: * The error message and the char counting message use the same id value * {% if add_input_form.input_name.errors %} {% for error in add_input_form.input_name.errors %}
{{ error }}
{% endfor %} {% else %}
140 characters left
{% endif %} */ export function displayCharactersCount( maxLimit: number, targetFieldId: string, notificationId: string ) { const targetField = document.getElementById( targetFieldId ) as HTMLInputElement, notificationMsgField = document.getElementById(notificationId); const checkCount = function (textLength: number) { if (textLength < 0) { // change color of text to red notificationMsgField.className = 'mmui-form-error-msg'; notificationMsgField.innerHTML = `${ -1 * textLength } characters over`; } else { notificationMsgField.innerHTML = `${textLength} characters left`; notificationMsgField.className = 'mmui-form-notification-msg'; } }; const charactersCount = function () { // unescape and encodeURIComponent together gave the correct number count compare to python str.encode('utf-8') const textLength = maxLimit - unescape(encodeURIComponent(targetField.value)).length; checkCount(textLength); }; const pageLoadCharactersCount = function () { // unescape and encodeURIComponent together gave the correct number count compare to python str.encode('utf-8') const textLength = maxLimit - unescape(encodeURIComponent(targetField.value)).length; // when page load, if the field has validation error, keep the error, otherwise check the characters count if (!notificationMsgField.classList.contains('mmui-form-error-msg')) { checkCount(textLength); } }; // check chars count then page loaded, useful for form validation return page document.addEventListener('DOMContentLoaded', pageLoadCharactersCount); targetField.addEventListener('keyup', charactersCount); } /** * Sets all the form inputs, under provided parentSelector parameter, * to enabled or disabled based on provided isEnabled parameter. * @param parentSelector * @param isEnabled */ export function updateFormInputsEnableState( parentSelector: string, isEnabled: boolean ) { let i, input: HTMLInputElement, inputs: NodeListOf, select: HTMLSelectElement, selects: NodeListOf, textarea: HTMLTextAreaElement, textareas: NodeListOf; inputs = >( document.querySelectorAll(`${parentSelector} input`) ); for (i = 0; i < inputs.length; i++) { input = inputs[i]; input.disabled = !isEnabled; } selects = >( document.querySelectorAll(`${parentSelector} select`) ); for (i = 0; i < selects.length; i++) { select = selects[i]; select.disabled = !isEnabled; } textareas = >( document.querySelectorAll(`${parentSelector} textarea`) ); for (i = 0; i < textareas.length; i++) { textarea = textareas[i]; textarea.disabled = !isEnabled; } } /** * Sets all the form inputs, under provided fieldSetId parameter, * to enabled or disabled based on provided isEnabled parameter. * @param fieldSetId * @param isEnabled */ export function updateFieldSetEnableState( fieldSetId: string, isEnabled: boolean ) { const fieldSet = document.getElementById(fieldSetId) as HTMLFieldSetElement; fieldSet.disabled = !isEnabled; } /** * Sets all the form inputs, under provided fieldSetId parameter, * to readonly or removes the readonly attribute based on provided isReadonly parameter. * @param fieldsetSelector * @param isReadonly */ export function updateFieldSetReadonlyState( fieldsetSelector: string, isReadonly: boolean ) { const fieldSet = document.querySelector( fieldsetSelector ) as HTMLFieldSetElement; if (isReadonly) { fieldSet.setAttribute('readonly', 'readonly'); } else { fieldSet.removeAttribute('readonly'); } const fieldsetElmts = document.querySelectorAll( `${fieldsetSelector} input, textarea` ); for (let i = 0; i < fieldsetElmts.length; i++) { if (isReadonly) { fieldsetElmts[i].setAttribute('readonly', 'readonly'); } else { fieldsetElmts[i].removeAttribute('readonly'); } } } /** * making drop choice data after parsing * @param formObj */ export function makeDropChoiceDataParser(formObj) { return function (checkedValue, index) { const dropChoiceDataClone = JSON.parse(JSON.stringify(formObj)); if (index > 0) { for (const choice of dropChoiceDataClone.choices) { choice.id = choice.id.replace('0', index); choice.name = choice.name.replace('0', index); choice.isChecked = choice.value == checkedValue; } } return dropChoiceDataClone; }; } export function parseDynamicForm(selector, fields) { const dataArray = [], dynamicFormObj: any = formObjToObj(formToObj(selector), false); let index = 0, value, name, fieldId, fieldErrorsId, fieldErrorElmts; let hasMoreFormRows = true; while (hasMoreFormRows) { const formRowObj = {}; for (const f of fields) { name = `${f}_${index}`; fieldId = `id_${name}`; fieldErrorsId = `${fieldId}_errors`; if (Object.prototype.hasOwnProperty.call(dynamicFormObj, name)) { value = dynamicFormObj[name]; formRowObj[f] = value; fieldErrorElmts = document.querySelectorAll( `#${fieldErrorsId} .mmui-form-error-msg` ); if (fieldErrorElmts.length > 0) { const fieldErrorsArray = []; for (const fieldErrorElmt of fieldErrorElmts) { fieldErrorsArray.push(fieldErrorElmt.innerText.trim()); } formRowObj[`${f}_errors`] = fieldErrorsArray; } } else { hasMoreFormRows = false; } } if (hasMoreFormRows) { dataArray.push(formRowObj); } index = index + 1; } return dataArray; }