//= require Sortable.min //= require canvas-to-blob.min (function(global) { 'use strict'; // Sets callbacks for toggling checkboxes function applyCheckboxCallBack() { $("[data-action='check-item']").off().on('click', function(e){ var checkboxitem = $(this).find("input"); var checked = checkboxitem.is(":checked"); $.ajax({ url: checkboxitem.data("link-url"), type: "POST", dataType: "json", data: {checklistitem_id: checkboxitem.data("id"), checked: checked}, success: function (data) { checkboxitem.prop("checked", checked); }, error: function (data) { checkboxitem.prop("checked", !checked); } }); }); } // Sets callback for completing/uncompleting step function applyStepCompletedCallBack() { // First, remove old event handlers, as we use turbolinks $("[data-action='complete-step'], [data-action='uncomplete-step']") .off().on('click', function(e){ var button = $(this); var step = $(this).parents(".step"); var completed = !step.hasClass("completed"); $.ajax({ url: button.data("link-url"), type: "POST", dataType: "json", data: {completed: completed}, success: function (data) { step.toggleClass("completed"); step.find(".toggle-step-complete[data-action='complete-step']").toggleClass('hidden'); step.find(".toggle-step-complete[data-action='uncomplete-step']").toggleClass('hidden'); step.find(".button-step-complete[data-action='complete-step']").toggleClass('hidden'); step.find(".button-step-complete[data-action='uncomplete-step']").toggleClass('hidden'); }, error: function(response) { if (response.status === 403) { HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger'); } } }); }); } function applyCancelCallBack() { //Click on cancel button $("[data-action='cancel-edit']") .on("ajax:success", function(e, data) { var $form = $(this).closest("form"); $form.after(data.html); var $new_step = $(this).next(); $(this).remove(); initCallBacks(); initHandsOnTable($new_step); toggleButtons(true); setTimeout(function() { initStepsComments(); SmartAnnotation.preventPropagation('.atwho-user-popover'); TinyMCE.destroyAll(); DragNDropSteps.clearFiles(); MarvinJsEditor.initNewButton('.new-marvinjs-upload-button'); }, 1000); }) .on("ajax:error", function(e, xhr, status, error) { // TODO: error handling }); } // Set callback for click on edit button function applyEditCallBack() { $("[data-action='edit-step']") .on("ajax:success", function(e, data) { var $step = $(this).closest(".step"); var $edit_step = $step.after(data.html); var $form = $step.next(); $step.remove(); formCallback($form); initEditableHandsOnTable($form); applyCancelCallBack(); formEditAjax($form); toggleButtons(false); initializeCheckboxSorting(); animateSpinner(null, false); DragNDropSteps.clearFiles(); if (tinyMCE.editors.step_description_textarea) tinyMCE.editors.step_description_textarea.remove(); TinyMCE.init('#step_description_textarea'); $form.on('change', '.checklist-item-text, .checklist-name', function() { $form.addClass(GLOBAL_CONSTANTS.HAS_UNSAVED_DATA_CLASS_NAME); }); $("#new-step-checklists fieldset.nested_step_checklists ul").each(function () { enableCheckboxSorting(this); }); $("#step_name").focus(); $("#new-step-main-tab a").on("shown.bs.tab", function() { $("#step_name").focus(); tinyMCE.editors.step_description_textarea.remove(); TinyMCE.init('#step_description_textarea'); }); }) .on("ajax:error", function(e, response) { if (response.status === 403) { HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger'); } }); } // Set callback for click on move step function applyMoveStepCallBack() { $('#steps').on('ajax:success', "[data-action='move-step']", function(e, data) { if ($.isEmptyObject(data)) return; let $step = $(this).closest('.step'); let steps = $('#steps').find('.step'); $('#steps').append($.map(data.steps_order, function(step_data) { let step = $('#steps').find(`.step[data-id=${step_data.id}]`); step.find('.step-number').html(`${step_data.position + 1}.`); return step; })); $('html, body').animate({ scrollTop: $step.offset().top - window.innerHeight / 2 }); if (typeof refreshProtocolStatusBar === 'function') refreshProtocolStatusBar(); }) .on("ajax:error", function(e, xhr) { if (xhr.status === 403) { HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger'); } }); } function formCallback($form) { $form .on("fields_added.nested_form_fields", function(e, param) { if (param.object_class == "table") { initEditableHandsOnTable($form); } }) .on("fields_removed.nested_form_fields", function(e, param) { if (param.object_class == "asset") { // Clear file input $(e.target).find("input[type='file']").val(""); } }); } // Init ajax success/error for edit form function formEditAjax($form) { $form .on("ajax:beforeSend", function () { $(".nested_step_checklists ul").each(function () { reorderCheckboxData(this); }); }) .on("ajax:success", function(e, data) { $(this).after(data.html); var $new_step = $(this).next(); $(this).remove(); initCallBacks(); initHandsOnTable($new_step); toggleButtons(true); TinyMCE.destroyAll(); SmartAnnotation.preventPropagation('.atwho-user-popover'); // Show the edited step $new_step.find(".panel-collapse:first").addClass("collapse in"); //Rerender tables $new_step.find("[data-role='step-hot-table']").each(function() { renderTable($(this)); }); }) .on("ajax:error", function(e, xhr, status, error) { $form.renderFormErrors('step', xhr.responseJSON, true, e); formCallback($form); initEditableHandsOnTable($form); applyCancelCallBack(); SmartAnnotation.preventPropagation('.atwho-user-popover'); //Rerender tables $form.find("[data-role='step-hot-table']").each(function() { renderTable($(this)); }); }); } function toggleButtons(show) { if (show) { $("[data-action='new-step']").show(); $("[data-action='edit-step']").show(); if ($('#steps .step').length > 0) { $(".expand-all-steps").show(); } } else { $("[data-action='new-step']").hide(); $("[data-action='edit-step']").hide(); } } // Creates handsontable where needed function initHandsOnTable(root) { root.find("[data-role='hot-table']").each(function() { var $container = $(this).find("[data-role='step-hot-table']"); var contents = $(this).find('.hot-contents'); $container.handsontable({ startRows: <%= Constants::HANDSONTABLE_INIT_ROWS_CNT %>, startCols: <%= Constants::HANDSONTABLE_INIT_COLS_CNT %>, rowHeaders: true, colHeaders: true, fillHandle: false, formulas: true, cells: function (row, col, prop) { var cellProperties = {}; if (col >= 0) cellProperties.readOnly = true; else cellProperties.readOnly = false; return cellProperties; } }); var hot = $container.handsontable('getInstance'); if (contents.attr("value")) { var data = JSON.parse(contents.attr("value")); if (Array.isArray(data.data)) hot.loadData(data.data); setTimeout(() => { hot.render() }, 0) } }); } // Init handsontable which can be edited function initEditableHandsOnTable(root) { root.find("[data-role='editable-table']").each(function() { var $container = $(this).find(".hot"); if ($container.is("[initialized]")) { return true; } var contents = $(this).find('.hot-contents'); var data = null; if (contents.attr("value")) { data = JSON.parse(contents.attr("value")).data; } if ($container.is(":visible")) { $(this).css("width", $("#new-step-tables").css("width")); } $container.handsontable({ data: data, startRows: <%= Constants::HANDSONTABLE_INIT_ROWS_CNT %>, startCols: <%= Constants::HANDSONTABLE_INIT_COLS_CNT %>, minRows: 1, minCols: 1, rowHeaders: true, colHeaders: true, contextMenu: true, formulas: true, preventOverflow: 'horizontal', afterChange: function() { $container.addClass(GLOBAL_CONSTANTS.HAS_UNSAVED_DATA_CLASS_NAME); } }); $container.attr("initialized", true); renderTable($container); }); $("#new-step-tables-tab a") .on("shown.bs.tab", function() { $(this).parents("form").find("div.hot").each(function() { $(this).parent().css("width", $("#new-step-tables").css("width")); renderTable($(this)); }); }); } function applyCancelOnNew() { $("[data-action='cancel-new']").click(function(event) { event.preventDefault(); event.stopPropagation(); event.stopImmediatePropagation(); var $form = $(this).closest("form"); $form.parent().remove().promise().done(function() { newStepHandler(); }); toggleButtons(true); DragNDropSteps.clearFiles(); tinyMCE.editors.step_description_textarea.remove(); }); } function initDeleteStep(){ $("[data-action='delete-step']").on("confirm:complete", function (e, answer) { if (answer) { animateLoading(); } }); } function initCallBacks() { if (typeof(applyCreateWopiFileCallback) === 'function') applyCreateWopiFileCallback(); applyCheckboxCallBack(); applyStepCompletedCallBack(); applyEditCallBack(); applyMoveStepCallBack(); initDeleteStep(); TinyMCE.highlight(); } /* * Correction for sorting with "Sortable.min" JS library to work correctly with * "nested_form_fields" gem. */ function reorderCheckboxData(checkboxUl) { // Make sure checkbox item insertion script is always at the bottom of "ul" // tag, otherwise item will not be inserted at bottom if(!$(checkboxUl).children().last().is('script')) { $(checkboxUl).find("script").appendTo(checkboxUl); } var $checkboxes = $(checkboxUl).find(".nested_fields"); $checkboxes.each(function (itemPos) { var $this = $(this); var $formGroup = $this.find(".form-group"); var $label = $formGroup.find(".control-label"); var $textInput = $formGroup.find(".checklist-item-text"); var $posInput = $formGroup.parent().find(".checklist-item-pos"); var $destroyLink = $this.find(".remove_nested_fields_link"); var labelFor = $label.attr("for"); var textName = $textInput.attr("name"); var textId = $textInput.attr("id"); var posName = $posInput.attr("name"); var posId = $posInput.attr("id"); var destroyLink = $destroyLink.attr("data-delete-association-field-name"); labelFor = labelFor.replace(/\d+_text/, itemPos + "_text"); textName = textName.replace(/\[\d+\]\[text\]/, "[" + itemPos + "][text]"); textId = textId.replace(/\d+_text/, itemPos + "_text"); posName = posName.replace(/\[\d+\]\[position\]/, "[" + itemPos + "][position]"); posId = posId.replace(/\d+_position/, itemPos + "_position"); destroyLink = destroyLink.replace(/\[\d+\]\[_destroy\]/, "[" + itemPos + "][_destroy]"); $label.attr("for", labelFor); $textInput.attr("name", textName); // Actually needed for sorting to work $textInput.attr("id", textId); $posInput.attr("name", posName); $posInput.attr("id", posId); $posInput.val(itemPos); $destroyLink.attr("data-delete-association-field-name", destroyLink); var $idInput = $this.find("> input"); if ($idInput.length) { var idName = $idInput.attr("name"); var idId = $idInput.attr("id"); idName = idName.replace(/\[\d+\]\[id\]/, "[" + itemPos + "][id]"); idId = idId.replace(/\d+_id/, itemPos + "_id"); $idInput.attr("name", idName); $idInput.attr("id", idId); } if ($this.css('display') == 'none') { // Actually needed for deleting to work var $destroyInput = $this.prev(); var destroyName = $destroyInput.attr("name"); destroyName = destroyName.replace(/\[\d+\]\[_destroy\]/, "[" + itemPos + "][_destroy]"); $destroyInput.attr("name", destroyName); } $formGroup.addClass(GLOBAL_CONSTANTS.HAS_UNSAVED_DATA_CLASS_NAME); }); } function enableCheckboxSorting(el) { Sortable.create(el, { draggable: 'fieldset', handle: '.fa-circle', onUpdate: function () { reorderCheckboxData(el); } }); } function initializeCheckboxSorting() { var el = $("#new-step-checklists a[data-association-path=step_checklists]"); el.click(function () { // calling code below must be defered because at this step HTML is not // inserted into DOM. setTimeout(function () { var list = el.parent().find("fieldset.nested_step_checklists:last ul"); enableCheckboxSorting(list.get(0)); }); }); } // New step AJAX function newStepHandler() { $("[data-action='new-step']").off().on('click', function(event) { event.preventDefault(); event.stopPropagation(); event.stopImmediatePropagation(); var $btn = $(this); $btn.off(); animateSpinner(null, true); $('#protocol-container').collapse('show'); $.ajax({ url: $btn.data('href'), method: 'GET', success: function(data) { var $form = $(data.html); $('#steps').append($form).promise().done(function() { animateSpinner(null, false); // Scroll to bottom $('html, body').animate({ scrollTop: $(document).height() - $(window).height() }); formCallback($form); applyCancelOnNew(); toggleButtons(false); initializeCheckboxSorting(); $('#step_name').focus(); $('#new-step-main-tab a') .on('shown.bs.tab', function() { $('#step_name').focus(); tinyMCE.editors.step_description_textarea.remove(); TinyMCE.init('#step_description_textarea'); }) TinyMCE.init('#step_description_textarea'); }); }, error: function(response) { if (response.status === 403) { HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger'); animateSpinner(null, false); } } }) }); } // Needed because server-side validation failure clears locations of // files to be uploaded and checklist's items etc. Also user // experience is improved global.processStep = function processStep(ev, editMode) { ev.stopPropagation(); ev.preventDefault(); ev.stopImmediatePropagation(); var $form = $(ev.target.form); $form.clearFormErrors(); $form.removeBlankFileForms(); var $checklists = $form.find(".nested_step_checklists"); var checklistsValid = checklistsValidator(ev, $checklists, editMode); var $nameInput = $form.find("#step_name"); var nameValid = textValidator(ev, $nameInput, 1, <%= Constants::NAME_MAX_LENGTH %>); var $descrTextarea = $form.find("#step_description_textarea"); var $tinyMCEInput = tinyMCE.editors.step_description_textarea.getContent(); var descriptionValid = textValidator(ev, $descrTextarea, 0, <%= Constants::RICH_TEXT_MAX_LENGTH %>, false, $tinyMCEInput); var tableNamesValidArray = []; // iterate over table titles and validate their length $form.find(".table_name").each(function(index , tableName) { if (tableName.value.length > 0) { tableNamesValidArray[index] = textValidator(ev, tableName, 1, <%= Constants::NAME_MAX_LENGTH %> ); } else { tableNamesValidArray[index] = true; } }); var tableNamesValid = true; // passes if all table names are valid for (var i=0;i { formData.append('step[description]', $tinyMCEInput); $.ajax({ url: $form.attr('action'), method: 'POST', data: formData, contentType: false, processData: false, success: function(data) { $($form.closest('.well')).after(data.html); var $new_step = $($form.closest('.well')).next(); $($form.closest('.well')).remove(); initCallBacks(); initHandsOnTable($new_step); expandStep($new_step); toggleButtons(true); SmartAnnotation.preventPropagation('.atwho-user-popover'); $new_step.find('.attachments').trigger('reorder'); tinyMCE.editors.step_description_textarea.remove(); MarvinJsEditor.initNewButton('.new-marvinjs-upload-button'); //Rerender tables $new_step.find("div.step-result-hot-table").each(function() { $(this).handsontable("render"); }); animateSpinner(null, false); DragNDropSteps.clearFiles(); if (typeof refreshProtocolStatusBar === 'function') refreshProtocolStatusBar(); }, error: function(xhr) { if (xhr.responseJSON['assets.file']) { $('#new-step-assets-group').addClass('has-error'); $('#new-step-assets-tab').addClass('has-error'); $.each(xhr.responseJSON['assets.file'], function(_, value) { $('#new-step-assets-group').prepend('' + value + ''); }); } animateSpinner(null, false); SmartAnnotation.preventPropagation('.atwho-user-popover'); } }); newStepHandler(); }).catch(() => { $('#new-step-assets-tab').addClass('has-error'); $('#new-step-assets-group') .addClass('has-error') .prepend('' + I18n.t('general.file.upload_failure') + ''); animateSpinner(null, false); }); } } // Expand all steps function expandAllSteps() { $('.step .panel-collapse').collapse('show'); $(document).find("[data-role='step-hot-table']").each(function() { renderTable($(this)); }); } function expandStep(step) { $('.panel-collapse', step).collapse('show'); $(step).find("div.step-result-hot-table").each(function() { renderTable($(this)); }); } function renderTable(table) { $(table).handsontable("render"); // Yet another dirty hack to solve HandsOnTable problems if (parseInt($(table).css('height'), 10) < parseInt($(table).css('max-height'), 10) - 30) { $(table).find('.ht_master .wtHolder').css({ 'height': '100%', 'width': '100%' }); } } function initStepsComments() { Comments.init(); } // On init initCallBacks(); initHandsOnTable($(document)); expandAllSteps(); TinyMCE.highlight(); SmartAnnotation.preventPropagation('.atwho-user-popover'); newStepHandler(); $(function () { $("[data-action='collapse-steps']").click(function () { $('.step .panel-collapse').collapse('hide'); }); $("[data-action='expand-steps']").click(expandAllSteps); }); global.initHandsOnTable = initHandsOnTable; })(window); (function() { // Reorder attachments function reorderAttachmentsInit() { $('#steps').on('click', '.attachments-actions .change-order', function(e){ var orderDropdown = $(this).closest('.dropdown-menu'); var assetsContainer = $(`.attachments[data-step-id=${orderDropdown.data('step-id')}]`) var order = $(this).data('order'); e.preventDefault(); assetsContainer.data('order', order); orderDropdown.find('.change-order').removeClass('selected'); $(this).addClass('selected'); assetsContainer.trigger('reorder'); $.post(orderDropdown.data('state-save-url'), { assets: { order: order } }); }) $('#steps').on('reorder', '.attachments', function() { var assets = $(`.attachments[data-step-id=${$(this).data('step-id')}] .asset`); var order = $(this).data('order'); var sortedAssets = assets.sort(function(a, b) { if (a.dataset.assetOrder == b.dataset.assetOrder) { if (order == 'new') { return b.dataset.assetUpdatedAt - a.dataset.assetUpdatedAt; } if (order == 'old') { return a.dataset.assetUpdatedAt - b.dataset.assetUpdatedAt; } if (order == 'atoz') { return a.dataset.assetFileName.toLowerCase() > b.dataset.assetFileName.toLowerCase() ? 1 : -1; } if (order == 'ztoa') { return b.dataset.assetFileName.toLowerCase() > a.dataset.assetFileName.toLowerCase() ? 1 : -1; } } return a.dataset.assetOrder > b.dataset.assetOrder ? 1 : -1 }) $.each(sortedAssets, function(i, element){ element.style.order = i }) }) .on('DOMSubtreeModified', '.attachments', function() { $(this).trigger('reorder'); }) $('.attachments').trigger('reorder'); } function initAssetViewModeToggle(){ $('#steps').on('click', '.attachments-actions .attachments-view-mode', function () { var viewModeBtn = $(this); $.post(viewModeBtn.closest('.dropdown-menu').data('view-mode-url'), { assets_view_mode: viewModeBtn.data('assets-view-mode') }, function(result) { viewModeBtn.closest('.dropdown-menu').find('.attachments-view-mode').removeClass('selected'); viewModeBtn.addClass('selected'); viewModeBtn.closest('.step').find('.attachments').html(result.html) }) }) } reorderAttachmentsInit(); initAssetViewModeToggle(); })();