let $editingField = null; let fieldIndex = 0; /** * @type {Map} */ const words = new Map(); /** * Handle the field type change events. * * @param {Event} event * @returns {void} */ function changeFieldType(event) { const fieldType = $(event.target).prop('value'); if(fieldType === 'text' || fieldType === 'textarea') { $('input[name="placeholder"]').closest('tr').removeClass('hidden'); $('.field-options').addClass('hidden'); revertOptions(); } else { revertOptions(); $('input[name="placeholder"]').closest('tr').addClass('hidden'); $('.field-options').removeClass('hidden'); } } /** * Fix options order. * * @returns {void} */ function fixOptionsOrder() { const forEach = function () { const index = $(this).children('span.input-group-addon').html().replace(/\d+$/, $(this).index() + 1); $(this).children('span.input-group-addon').html(index); } $('.options-container').children().each(forEach); } /** * Handle the remove option button Click event. * * @param {Event} event * @returns {void} */ function removeFieldOption(event) { $(event.target).closest('.input-group').remove(); fixOptionsOrder(); } /** * Handle the add option button Click event. * * @param {Event} event * @returns {void} */ function addFieldOption(event) { const template = document.getElementById('option-template'); const $clone = $(document.importNode(template.content, true)); $(event.target).closest('.options-container').append($clone); fixOptionsOrder(); } /** * Revert options. * * @returns {void} */ function revertOptions() { const template = document.getElementById('option-template'); const $optionsContainer = $('
'); const $option = $(document.importNode(template.content, true)); $optionsContainer.append($option); const $optionStr = $optionsContainer.html(); $optionsContainer.append($optionStr); $optionsContainer.append($optionStr); $optionsContainer.find('.input-group').first().find('button').last().attr('disabled', 'disabled'); $('.field-options') .children('td') .empty() .append($optionsContainer); fixOptionsOrder(); } /** * Handle the new field button click event. * * @returns {void} */ function toAddField() { const $modal = $('#add-field-modal'); const $modalBody = $modal.find('.modal-body'); $modalBody.empty(); const template = document.getElementById('field-template'); const clone = document.importNode(template.content, true); $modalBody.append(clone); $('#add-field-modal .text-danger').remove(); $modal.modal('show', 'fit'); } /** * Get the editing field. * * @param {*} $fieldTr * @returns {[boolean, *]} */ function getEditingField($fieldTr) { const name = $fieldTr.find('.field-name').html(); const isTextType = !$fieldTr.find('.picker').length; if(isTextType) { const type = $fieldTr.find('input.field-type').length ? 'text' : 'textarea'; const placeholder = $fieldTr.find('.field-type').prop('placeholder'); const required = $fieldTr.find('.field-type').closest('td').hasClass('required'); return [true, { name, type, placeholder, required }]; } const $picker = $fieldTr.find('.picker'); const type = $picker.hasClass('picker-multi') ? 'checkbox' : 'radio'; const options = $picker.attr('data-options').split(','); const required = $picker.closest('td').hasClass('required'); return [false, { name, type, options, required }]; } /** * Handle the edit field button click event. * * @param {Event} event * @returns {void} */ function toEditField(event) { const $fieldTr = $(event.target).closest('tr'); $editingField = $fieldTr; const [isTextType, field] = getEditingField($fieldTr); const $modal = $('#edit-field-modal'); const $modalBody = $modal.find('.modal-body'); $modalBody.empty(); const template = document.getElementById('field-template'); const $clone = $(document.importNode(template.content, true)); $modalBody.append($clone); $modalBody.find('input[name="field-name"]').prop('value', field.name); $modalBody.find('select[name="field-type"]').prop('value', field.type); if(isTextType) { $modalBody.find('input[name="placeholder"]').prop('value', field.placeholder); $('input[name="placeholder"]').closest('tr').removeClass('hidden'); $('.field-options').addClass('hidden'); } else { const $optionsWrapper = $modalBody.find('.field-options > td') $optionsWrapper.empty(); const $optionsContainer = $(`
`) $optionsWrapper.append($optionsContainer); field.options.forEach((option) => { const template = document.getElementById('option-template'); const $clone = $(document.importNode(template.content, true)); $optionsContainer.append($clone); $optionsContainer.children('.input-group').last().find('input[name="option[]"]').prop('value', option); }); fixOptionsOrder(); $('input[name="placeholder"]').closest('tr').addClass('hidden'); $('.field-options').removeClass('hidden'); } if(field.required) { $modalBody.find('input[value="1"]').attr('checked', 'checked'); $modalBody.find('input[value="0"]').removeAttr('checked'); } else { $modalBody.find('input[value="0"]').attr('checked', 'checked'); $modalBody.find('input[value="1"]').removeAttr('checked'); } $modal.find('.input-group').first().find('button').last().attr('disabled', 'disabled'); $('#edit-field-modal .text-danger').remove(); $modal.modal('show', 'fit'); } /** * Handle the remove field button click event. * * @param {Event} event * @returns {void} */ function toRemoveField(event) { const result = window.confirm(deleteTip); if(!result) return; const $tr = $(event.target).closest('tr'); const fieldID = $tr.attr('data-id'); $tr.remove(); $('.field-content').find(`[data-id="${fieldID}"]`).remove(); if($('.field-configuration').children().length === 0) $('.field-configuration').parent().addClass('hidden').prev().removeClass('hidden'); words.delete($tr.find('th').prop('title')); const innerHTML = $('#autocomplete-textarea').html(); $('#autocomplete-textarea').html(innerHTML.replace(//g, '').replace(/<\/strong>/g, '')); updatePromptPreview(); } /** * Check for duplicate field names. * * @param {string} name * @returns {boolean} result */ const checkDuplicatedFieldName = (name) => Array.from(words.keys()).includes(name); /** * Show field name error iferror exists. * * @param {*} $required * @param {HTMLFormElement} formData * @returns {boolean} result */ function showFieldNameErrorIfExist($required, form) { const $form = $(form); const formData = new FormData(form); const name = formData.get('field-name'); $form.find('.text-danger.help-text').remove(); $form.find('.has-error').removeClass('has-error'); if(!name.trim()) { $required .addClass('has-error') .append(`
${emptyWarning.replace('%s', fieldName)}
`); return true; } if(checkDuplicatedFieldName(name)) { if(!$editingField || $editingField.find('.field-name').html() !== name) { $required .addClass('has-error') .append(`
${duplicatedWarning.replace('%s', fieldName)}
`); return true; } } if(['radio', 'checkbox'].includes(formData.get('field-type')) && formData.getAll('option[]').filter(x => !!x).length === 0) { $form.find('.options-container').children().first().addClass('has-error').after(`
${emptyOptionWarning}
`); return true; } return false; } /** * Handle the save field button click event. * * @returns {void} */ function saveNewField() { const form = document.querySelector('#add-field-modal form'); const formData = new FormData(form); const name = formData.get('field-name'); const $required = $('#add-field-modal input[name="field-name"]').closest('.required'); if(showFieldNameErrorIfExist($required, form)) return; $('#add-field-modal').modal('hide'); const $fieldView = createFieldView(formData); $fieldView.attr('data-id', `field-${++fieldIndex}`); $('#sortable-list').append($fieldView); $fieldView.find('.picker').addClass('disabled'); const $button = $('.field-configuration-main > div'); const $table = $('.field-configuration-main > table'); if(!$button.hasClass('hidden')) $button.addClass('hidden'); if($table.hasClass('hidden')) $table.removeClass('hidden'); const $fieldViewClone = createFieldView(formData); $fieldViewClone.attr('data-id', $fieldView.attr('data-id')); $fieldViewClone.children().last().remove(); $fieldViewClone.find('.drag-area').removeClass('darg-area'); $fieldViewClone.removeClass('srotable-item'); $fieldViewClone.find('.icon-move').remove(); $fieldViewClone.find('[readonly]').removeAttr('readonly'); $fieldViewClone.find('.field-type').on('change', updatePromptPreview); $('.field-content').append($fieldViewClone); words.set(name, $fieldView.attr('data-id')); } /** * Handle save edited field button click event. * * @returns {void} */ function saveEditedField() { const oldName = $editingField.find('.field-name').html(); const form = document.querySelector('#edit-field-modal form'); const formData = new FormData(form); const name = formData.get('field-name'); const $required = $('#edit-field-modal input[name="field-name"]').closest('.required'); if(showFieldNameErrorIfExist($required, form)) return; $('#edit-field-modal').modal('hide'); const $fieldView = createFieldView(formData); $fieldView.attr('data-id', $editingField.attr('data-id')); $editingField.replaceWith($fieldView); $fieldView.find('.picker').addClass('disabled'); const $fieldViewClone = createFieldView(formData); $fieldViewClone.attr('data-id', $fieldView.attr('data-id')); $fieldViewClone.children().last().remove(); $fieldViewClone.children().first().removeClass('darg-area'); $fieldViewClone.find('.icon-move').remove(); $fieldViewClone.find('[readonly]').removeAttr('readonly'); $fieldViewClone.find('.field-type').on('change', updatePromptPreview); $(`.field-content [data-id="${$editingField.attr('data-id')}"]`).replaceWith($fieldViewClone); words.delete(oldName); words.set(name, $fieldView.attr('data-id')); if(oldName !== name) updatePromptPreview(); $editingField = null; } /** * Create field view. * * @param {FormData} formData * @returns {*} */ function createFieldView(formData) { const $tr = $(``); $tr.append(createFieldNameView(formData)); $tr.append(createFieldTypeView(formData)); $tr.append(createFieldButtons(formData)); return $tr; } /** * Create field name view. * * @param {FormData} formData * @returns {*} */ function createFieldNameView(formData) { const name = formData.get('field-name'); return $(`
${name}
`); } /** * Create field type view. * * @param {FormData} formData * @returns {*} */ function createFieldTypeView(formData) { const type = formData.get('field-type'); const required = formData.get('field-required') === '1'; const $td = $(``); if(required) $td.addClass('required'); if(type === 'text') { const placeholder = formData.get('placeholder') || pleaseInput; $td.append(``); return $td; } if(type === 'textarea') { const placeholder = formData.get('placeholder') || pleaseInput; $td.append(`