diff --git a/app/assets/javascripts/hyrax/save_work/required_fields.es6 b/app/assets/javascripts/hyrax/save_work/required_fields.es6 index 8b1a9f22d..97aa7b698 100644 --- a/app/assets/javascripts/hyrax/save_work/required_fields.es6 +++ b/app/assets/javascripts/hyrax/save_work/required_fields.es6 @@ -60,4 +60,17 @@ export class RequiredFields { this.requiredFields = this.form.find(':input[required], input[name*="date_issued"]'); this.requiredFields.change(this.callback) } + + // [hyc-override] Save TinyMCE content to the textarea before checking for required fields + // Important for required fields that are TinyMCE editors + saveTinyMCEContent() { + // Loop through each TinyMCE instance in the form + $(this.form).find('.tinymce').each((index, elem) => { + const id = $(elem).attr('id'); + // If the TinyMCE instance exists, save the content + if (id && tinymce.get(id)) { + tinymce.get(id).save(); + } + }); + } } \ No newline at end of file diff --git a/app/assets/javascripts/hyrax/save_work/save_work_control.es6 b/app/assets/javascripts/hyrax/save_work/save_work_control.es6 new file mode 100644 index 000000000..5a4d8393e --- /dev/null +++ b/app/assets/javascripts/hyrax/save_work/save_work_control.es6 @@ -0,0 +1,187 @@ +import { RequiredFields } from './required_fields' +import { ChecklistItem } from './checklist_item' +import { UploadedFiles } from './uploaded_files' +import { DepositAgreement } from './deposit_agreement' +import VisibilityComponent from './visibility_component' + +/** + * Polyfill String.prototype.startsWith() + */ +if (!String.prototype.startsWith) { + String.prototype.startsWith = function(searchString, position){ + position = position || 0; + return this.substr(position, searchString.length) === searchString; + }; +} + +export default class SaveWorkControl { + /** + * Initialize the save controls + * @param {jQuery} element the jquery selector for the save panel + * @param {AdminSetWidget} adminSetWidget the control for the adminSet dropdown + */ + constructor(element, adminSetWidget) { + if (element.length < 1) { + return + } + this.element = element + this.adminSetWidget = adminSetWidget + this.form = element.closest('form') + element.data('save_work_control', this) + this.activate(); + } + + /** + * Keep the form from submitting (if the return key is pressed) + * unless the form is valid. + * + * This seems to occur when focus is on one of the visibility buttons + */ + preventSubmitUnlessValid() { + this.form.on('submit', (evt) => { + if (!this.isValid()) + evt.preventDefault(); + }) + } + + /** + * Keep the form from being submitted many times. + * + */ + preventSubmitIfAlreadyInProgress() { + this.form.on('submit', (evt) => { + if (this.isValid()) + this.saveButton.prop("disabled", true); + }) + } + + /** + * Keep the form from being submitted while uploads are running + * + */ + preventSubmitIfUploading() { + this.form.on('submit', (evt) => { + if (this.uploads.inProgress) { + evt.preventDefault() + } + }) + } + + /** + * Is the form for a new object (vs edit an existing object) + */ + get isNew() { + return this.form.attr('id').startsWith('new') + } + + /* + * Call this when the form has been rendered + */ + activate() { + if (!this.form) { + return + } + this.requiredFields = new RequiredFields(this.form, () => this.formStateChanged()) + this.uploads = new UploadedFiles(this.form, () => this.formStateChanged()) + this.saveButton = this.element.find(':submit') + this.depositAgreement = new DepositAgreement(this.form, () => this.formStateChanged()) + this.requiredMetadata = new ChecklistItem(this.element.find('#required-metadata')) + this.requiredFiles = new ChecklistItem(this.element.find('#required-files')) + this.requiredAgreement = new ChecklistItem(this.element.find('#required-agreement')) + new VisibilityComponent(this.element.find('.visibility'), this.adminSetWidget) + this.preventSubmit() + this.watchMultivaluedFields() + this.formChanged() + this.addFileUploadEventListeners(); + } + + addFileUploadEventListeners() { + let $uploadsEl = this.uploads.element; + const $cancelBtn = this.uploads.form.find('#file-upload-cancel-btn'); + + $uploadsEl.on('fileuploadstart', () => { + $cancelBtn.hidden = false; + }); + + $uploadsEl.on('fileuploadstop', () => { + $cancelBtn.hidden = true; + }); + } + + preventSubmit() { + this.preventSubmitUnlessValid() + this.preventSubmitIfAlreadyInProgress() + this.preventSubmitIfUploading() + } + + // If someone adds or removes a field on a multivalue input, fire a formChanged event. + watchMultivaluedFields() { + $('.multi_value.form-group', this.form).on('managed_field:add', () => this.formChanged()) + $('.multi_value.form-group', this.form).on('managed_field:remove', () => this.formChanged()) + } + + // Called when a file has been uploaded, the deposit agreement is clicked or a form field has had text entered. + formStateChanged() { + this.saveButton.prop("disabled", !this.isSaveButtonEnabled); + } + + // called when a new field has been added to the form. + formChanged() { + this.requiredFields.reload(); + this.formStateChanged(); + } + + // Indicates whether the "Save" button should be enabled: a valid form and no uploads in progress + get isSaveButtonEnabled() { + return this.isValid() && !this.uploads.inProgress; + } + + isValid() { + // avoid short circuit evaluation. The checkboxes should be independent. + let metadataValid = this.validateMetadata() + let filesValid = this.validateFiles() + let agreementValid = this.validateAgreement(filesValid) + return metadataValid && filesValid && agreementValid + } + + // sets the metadata indicator to complete/incomplete + validateMetadata() { + // [hyc-override] Update textarea for fields using TinyMCE for rich text editing + this.requiredFields.saveTinyMCEContent() + + if (this.requiredFields.areComplete) { + this.requiredMetadata.check() + return true + } + this.requiredMetadata.uncheck() + return false + } + + // sets the files indicator to complete/incomplete + validateFiles() { + if (!this.uploads.hasFileRequirement) { + return true + } + if (!this.isNew || this.uploads.hasFiles) { + this.requiredFiles.check() + return true + } + this.requiredFiles.uncheck() + return false + } + + validateAgreement(filesValid) { + if (filesValid && this.uploads.hasNewFiles && this.depositAgreement.mustAgreeAgain) { + // Force the user to agree again + this.depositAgreement.setNotAccepted() + this.requiredAgreement.uncheck() + return false + } + if (!this.depositAgreement.isAccepted) { + this.requiredAgreement.uncheck() + return false + } + this.requiredAgreement.check() + return true + } +}