All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.yadaframework.views.yada.js.yada.ajax.js Maven / Gradle / Ivy

There is a newer version: 0.7.7.R4
Show newest version
// yada.ajax.js
// Depends on yada.js

(function( yada ) {
	"use strict";
	
	// Namespace trick explained here: http://stackoverflow.com/a/5947280/587641
	// For a public property or function, use "yada.xxx = ..."
	// For a private property use "var xxx = "
	// For a private function use "function xxx(..."
	
	const markerAjaxButtonOnly = 'yadaAjaxButtonOnly';
	const markerAjaxModal = 'yadaAjaxModal';
	const markerDropTarget = 'yadaDropTarget';
	var clickedButton;
		
	var ajaxCounter = 0; // Counter for the ajax call parallelism

	// WARNING: if you change this, also change it in yada.js
	const markerClass = 'yadaAjaxed'; // To prevent double submission
	
	// Deprecated: these were once used when opening the login form via yada.openLoginModal and yada.openLoginModalAjax
	yada.postLoginHandler = null; // Handler to run after login, if any
	var postLoginUrl = null;
	var postLoginData = null;
	var postLoginType = null;
	
	const yadaAjaxResponseHtmlRoot = "
"; yada.markerAjaxModal = markerAjaxModal; // For use in other scripts /** * Init yada ajax handlers on the specified element * @param $element the element, or null for the entire body */ yada.initAjaxHandlersOn = function($element) { yada.enableAjaxForms(null, $element); yada.enableAjaxLinks(null, $element); yada.enableAjaxSelects(null, $element); yada.enableAjaxCheckboxes(null, $element); // Maybe obsoleted by yada.enableAjaxInputs yada.enableDropUpload(null, $element); initObservers($element) yada.enableAjaxInputs(); } /** * All observers should be enabled in this function, that is also called after a returned ajax HTML is cloned. */ function initObservers($element) { yada.enableAjaxTriggerInViewport($element); } ////////////////////// /// Pagination support /** * Changes the current URL history adding pagination parameters: the back button will enforce the previous page parameters on the url. * This is needed when loading a new page via ajax so that the browser back button will run the correct query again. * Pagination parameters on the nextpage url/form must always be named "page" and "size", but they can be different on the history url in order to have * more than one pageable section on the same page, for example "product.page" and "project.page". * @param $linkOrForm the jQuery object of the clicked anchor, or submitted form, with pagination parameters named "page" and "size" * @param pageParam the optional name of the parameter that will contain the page number to load on back, for example "product.page" * @param sizeParam the optional name of the parameter that will contain the page size to load on back, for example "product.size" * @param loadPreviousParam the optional name of the parameter that will contain the loadPrevious flag, for example "product.loadPrevious". * It just tells when the user pressed the back button, so that eventually all previous pages can be loaded, not just the last one. * @see net.yadaframework.web.YadaPageRequest */ yada.fixPaginationLinkHistory = function($linkOrForm, pageParam, sizeParam, loadPreviousParam) { const NEXTPAGE_NAME="page"; const NEXTSIZE_NAME="size"; const CONTAINERID_NAME="yadaContainer"; const CONTAINERSCROLL_NAME="yadaScroll"; // Default param names pageParam = pageParam || "page"; sizeParam = sizeParam || "size"; loadPreviousParam = loadPreviousParam || "loadPrevious"; // // ".../en/search/loadMoreProducts?searchString=tolo&page=2&size=4" const nextPageUrl = $linkOrForm.attr("data-yadahref") || $linkOrForm.attr("href"); var nextPage = yada.getUrlParameter(nextPageUrl, NEXTPAGE_NAME); var nextSize = yada.getUrlParameter(nextPageUrl, NEXTSIZE_NAME); if (nextPageUrl==null) { // Could be a form nextPage = $("input[name="+NEXTPAGE_NAME+"]", $linkOrForm).val() || 1; nextSize = $("input[name="+NEXTSIZE_NAME+"]", $linkOrForm).val() || 32; } const currentUrl = window.location.href; var newUrl = yada.addOrUpdateUrlParameter(currentUrl, pageParam, nextPage); newUrl = yada.addOrUpdateUrlParameter(newUrl, sizeParam, nextSize); newUrl = yada.addOrUpdateUrlParameter(newUrl, loadPreviousParam, true); // This is always true // Add the container id and the scroll position. // We presume that the scrolling element is the parent of the update target const updateTargetSelector = $linkOrForm.attr("data-yadaUpdateOnSuccess"); const $container = yada.extendedSelect($linkOrForm, updateTargetSelector).parent(); var containerId = $container.attr("id"); if (containerId!=null) { // If there is no id, there is no autoscroll (which is both easier to implement and a way to turn off the scroll behavior somehow) const scrollPos = $container.scrollTop(); newUrl = yada.addOrUpdateUrlParameter(newUrl, CONTAINERID_NAME, containerId); newUrl = yada.addOrUpdateUrlParameter(newUrl, CONTAINERSCROLL_NAME, scrollPos); } history.pushState({}, "", newUrl); }; /** * If the "data-yadaPaginationHistory" attribute is present, set a new history entry. * @return true if the attribute is present. */ function handlePaginationHistoryAttribute($elem, $linkOrForm) { var yadaPagination = $elem.attr("data-yadaPaginationHistory"); // ="pageParam, sizeParam, loadPreviousParam" if (yadaPagination==null) { return false; } if (yadaPagination=="") { yadaPagination=null; } const paginationParams = yada.listToArray(yadaPagination); yada.fixPaginationLinkHistory($linkOrForm, paginationParams[0], paginationParams[1], paginationParams[2]); return true; } //////////////////// /// Modal /** * Open a modal when the location.hash contains the needed value. * Example: openModalOnHash('/mymodal', ['id', 'name'], '/', function(data){return !isNaN(data.id);} * @param targetUrl the modal url to open via ajax; can have url parameters * @param paramNames an array of request parameter names that are assigned to from the hash * @param separator the values contained in the hash are separated by this character * @param validator a function that returns true if the hash values are valid */ yada.openModalOnHash = function(targetUrl, paramNames, separator, validator) { var hashValue = document.location.hash; if (hashValue!=null && hashValue.length>1) { try { var data = yada.hashPathToMap(paramNames, hashValue, separator); if (typeof validator == "function" && validator(data)) { yada.ajax(targetUrl, data); } } catch(e) { console.error(e); } } } //////////////////// /// Form // This is for coupled selects yada.enableAjaxSelectOptions = function() { $('.s_chain select').change(function() { var selectedValue = $('option:selected', this).val(); var $triggerSelectContainer = $(this).parents('.s_chain'); var triggerContainerId = $triggerSelectContainer.attr('id'); if (triggerContainerId) { var $targetSelectContainer = $triggerSelectContainer.siblings('[data-trigger-id='+triggerContainerId+']'); if ($targetSelectContainer) { var targetUrl = $targetSelectContainer.attr('data-url'); var targetExclude = $targetSelectContainer.attr('data-exclude-id'); var selectedOption = $targetSelectContainer.attr('data-selected-option'); var data={triggerId : selectedValue, excludeId : targetExclude}; yada.ajax(targetUrl, data, function(responseText) { $('select', $targetSelectContainer).children().remove(); $('select', $targetSelectContainer).append(responseText); if (selectedOption) { $('select option[value="'+selectedOption+'"]', $targetSelectContainer).prop('selected', true); } $('select', $targetSelectContainer).prop('disabled', false); }); } } }); }; // @Deprecated. Should use the generic modal instead of the login modal function openLoginModalIfPresent(responseHtml) { var loadedLoginModal=$(responseHtml).find("#loginModal"); if (loadedLoginModal.length>0) { var currentLoginModal = $('#loginModal.in'); if (currentLoginModal.length>0) { // The login modal is already open: just update the content var currentLoginDialog = $('#loginModalDialog', currentLoginModal); currentLoginDialog.replaceWith($('#loginModalDialog', loadedLoginModal)); $('#username').focus(); } else { // Open a new login modal const $existingModals = $(".modal.show"); $existingModals.modal("hide"); // Remove the background too $existingModals.remove(); // Remove any existing modals, stick modals too because their content may need to change after login $("#loginModal").remove(); // Just in case $("body").append(loadedLoginModal); $("#loginModal").on('shown.bs.modal', function (e) { $('#username').focus(); }) $('#loginModal').modal('show'); } yada.enableAjaxForm($('#loginForm'), null); // Login POST via Ajax return true; } return false; } /** * Returns true if a node has been removed from the DOM * @param $someNode a jquery HTML element */ function isRemoved($someNode) { return $someNode.closest("html").length==0; } // Al ritorno di un post di login, mostra eventuali notify ed esegue l'eventuale handler, oppure ricarica la pagina corrente se l'handler non c'è. // @Deprecated. Should use the generic modal instead of the login modal yada.handlePostLoginHandler = function(responseHtml, responseText) { var isError = yada.isNotifyError(responseHtml); yada.handleNotify(responseHtml); if (yada.postLoginHandler != null) { if (!isError) { // Esegue l'handler solo se non ho ricevuto una notifica di errore yada.postLoginHandler(responseText, responseHtml); } } else { // Not good: reload or not reload is application specific console.error("YadaWarning: deprecated page reload after ajax login") // If you really need to reload the page, do it in the login form successHandler debugger; // Set a debugger point here otherwise the above message is lost on page reload yada.loaderOn(); window.location.href=yada.removeHash(window.location.href); // Ricarico la pagina corrente (senza ripetere la post) se non ho un handler } yada.postLoginHandler = null; }; // Apre il modal del login se è già presente in pagina. // handler viene chiamato quando il login va a buon fine. // return true se il modal è presente ed è stato aperto, false se il modal non c'è e non può essere aperto. // @Deprecated. Should use the generic modal instead of the login modal yada.openLoginModal = function(url, data, handler, type) { if ($('#loginModal').length>0) { // ?????????? A cosa servono questi postXXXX ?????????????????? postLoginUrl = url; postLoginData = data; yada.postLoginHandler = handler; postLoginType = type; $("#loginModal").on('shown.bs.modal', function (e) { $('#username').focus(); }) $('#loginModal').modal('show'); return true; } return false; } // Apre il modal del login caricandolo via ajax. // handler viene chiamato quando il login va a buon fine // @Deprecated. Should use the generic modal instead of the login modal yada.openLoginModalAjax = function(loginFormUrl, handler, errorTitle, errorText) { yada.postLoginHandler = handler; $.get(loginFormUrl, function(responseText, statusText) { var responseHtml=$(yadaAjaxResponseHtmlRoot).html(responseText); var loginReceived = openLoginModalIfPresent(responseHtml); if (!loginReceived) { yada.showErrorModal(errorTitle, errorText); } }); } // Chiama la funzione javascript yadaCallback() se presente nell'html ricevuto dal server. // - responseHtml = l'html ricevuto dal server, creato con $("
").html(responseText) yada.callYadaCallbackIfPresent = function(responseHtml) { // Cerco se c'è una funzione js da eseguire chiamata "yadaCallback". var scriptNodes = $(responseHtml).find("script#yadaCallback"); if (scriptNodes.length>0) { $('#callbackJavascript').append(scriptNodes); yadaCallback(); return true; } return false; } function hasNoLoader($element) { return $element.hasClass("noLoader") || $element.hasClass("noloader") || $element.hasClass("yadaNoLoader") || $element.hasClass("yadaNoloader") || $element.hasClass("yadanoloader"); } const ajaxTriggerInViewportObserver = new IntersectionObserver(entries => { entries.forEach(entry => { if (entry.intersectionRatio > 0) { // console.log("firing " + $(entry.target).attr("data-yadahref")); ajaxTriggerInViewportObserver.unobserve(entry.target); // Fires once only makeAjaxCall(null, $(entry.target)); } }) }) /** * Enables the triggering of ajax calls when the element is entering the viewport (or is already in the viewport). * @param $element the dom section where to look for elements to enable, can be null for the entire body */ yada.enableAjaxTriggerInViewport = function($element) { if ($element==null || $element=="") { $element = $('body'); } var $target = $element.parent(); if ($target.length==0) { $target = $element; } $('[data-yadaTriggerInViewport]', $target).each(function() { var fetchUrl = $(this).attr("data-yadaHref") || $(this).attr("href"); if (fetchUrl!=null) { ajaxTriggerInViewportObserver.observe(this); // console.log("Observing " + $(this).attr("data-yadahref")); } }); }; /** * Transform links and non-submit buttons into ajax links: all anchors/buttons with a class of "yadaAjax" will be sent via ajax. * @param handler a function to call upon successful link submission, can be null * @param $element the element on which to enable ajax links, can be null for the entire body */ yada.enableAjaxLinks = function(handler, $element) { if ($element==null || $element=="") { $element = $('body'); } var $target = $element.parent(); if ($target.length==0) { $target = $element; } $('a.yadaAjax, button.yadaAjax:not([type="submit"])', $target).each(function() { $(this).removeClass('yadaAjax'); yada.enableAjaxLink($(this), handler); }); // Legacy $('.s_ajaxLink', $target).each(function() { $(this).removeClass('s_ajaxLink'); yada.enableAjaxLink($(this), handler); }); }; /** * Enables ajax on a checkbox change. Will either submit a parent form or make an ajax call directly. */ // Legacy version yada.enableAjaxCheckboxes = function(handler, $element) { if ($element==null || $element=="") { $element = $('body'); } var $target = $element.parent(); if ($target.length==0) { $target = $element; } $("input[type='checkbox'].yadaAjax", $target).each(function() { $(this).removeClass('yadaAjax'); yada.enableAjaxCheckbox($(this), handler); }); }; // Legacy version yada.enableAjaxCheckbox = function($checkbox, handler) { // If array, recurse to unroll if ($checkbox.length>1) { $checkbox.each(function() { yada.enableAjaxCheckbox($(this), handler); }); return; } // From here on the $checkbox is a single element, not an array $checkbox.not('.'+markerClass).change(function(e) { $checkbox = $(this); // Needed otherwise $checkbox could be stale (from a previous ajax replacement) // If there is a parent form, submit it, otherwise make an ajax call defined on the checkbox var $form = $checkbox.parents("form.yadaAjaxed"); if ($form.length>0) { $form.submit(); return; } return makeAjaxCall(e, $checkbox, handler); }) $checkbox.removeClass('yadaAjax'); $checkbox.not('.'+markerClass).addClass(markerClass); }; /** * Enables ajax calls on select change. * @param handler a function to call upon successful link submission, can be null * @param $element the element on which to enable ajax, can be null for the entire body */ // TODO this may conflict with yada.enableAjaxInputs and should be replaced with that one if possible // Legacy version yada.enableAjaxSelects = function(handler, $element) { if ($element==null || $element=="") { $element = $('body'); } var $target = $element.parent(); if ($target.length==0) { $target = $element; } $('select.yadaAjax', $target).each(function() { $(this).removeClass('yadaAjax'); yada.enableAjaxSelect($(this), handler); }); }; // Legacy version yada.enableAjaxSelect = function($select, handler) { // If array, recurse to unroll if ($select.length>1) { $select.each(function() { yada.enableAjaxSelect($(this), handler); }); return; } // From here on the $select is a single element, not an array $select.not('.'+markerClass).change(function(e) { $select = $(this); // Needed otherwise $select could be stale (from a previous ajax replacement) return makeAjaxCall(e, $select, handler); }) $select.removeClass('yadaAjax'); $select.not('.'+markerClass).addClass(markerClass); }; /** * Sends a link/button via ajax, it doesn't have to have class .yadaAjax. * Buttons must have a yada-href attribute and not be submit buttons. * Links with a "yadaLinkDisabled" class are disabled. * @param $link the jquery anchor or button (could be an array), e.g. $('.niceLink') * @param handler funzione chiamata in caso di successo e nessun yadaWebUtil.modalError() */ // See yada.enableAjaxLinks yada.enableAjaxLink = function($link, handler) { // If array, recurse to unroll if ($link.length>1) { $link.each(function() { yada.enableAjaxLink($(this), handler); }); return; } // From here on the $link is a single anchor, not an array $link.not('.'+markerClass).click(function(e) { $link = $(this); // Needed otherwise $link could be stale (from a previous ajax replacement) // Fix pagination parameters if any handlePaginationHistoryAttribute($link, $link); // return makeAjaxCall(e, $link, handler); }) $link.removeClass('yadaAjax'); $link.removeClass('s_ajaxLink'); // Legacy $link.not('.'+markerClass).addClass(markerClass); }; function handleDragenterDragover(event) { this.classList.add('yadaDragOver'); event.preventDefault(); event.stopPropagation(); } function handleDragleaveDragend(event) { this.classList.remove('yadaDragOver'); event.preventDefault(); event.stopPropagation(); } function handleDrop($dropTarget, handler, event) { let files = event.originalEvent.dataTransfer.files; if (files) { handleDroppedFiles(event, files, $dropTarget, handler); } } // Needed to remove the hanlder later var handleDropProxy = null; /** * Enables file upload by drag&drop. */ yada.enableDropUpload = function(handler, $element) { if ($element==null || $element=="") { $element = $('body'); } // If array, recurse to unroll if ($element.length>1) { $element.each(function() { yada.enableDropUpload(handler, $(this)); }); return; } $(`[data-yadaDropUpload]:not(.${markerDropTarget})`, $element).each(function() { const $dropTarget = $(this); $dropTarget.addClass(markerDropTarget); const url = $dropTarget.data('yadadropupload'); if (url!=null) { handleDropProxy = jQuery.proxy(handleDrop, null, $dropTarget, handler); $dropTarget.on('dragenter dragover', handleDragenterDragover); $dropTarget.on('dragleave dragend drop', handleDragleaveDragend); // Is "drop" needed here? $dropTarget.on('drop', handleDropProxy); } }); } function handleDroppedFiles(event, files, $dropTarget, handler) { const singleFileOnly = $dropTarget.attr("data-yadaSingleFileOnly"); // Title,message if (singleFileOnly!=null && files.length>1) { if (singleFileOnly=="") { singleFileOnly = "File Upload Error,Too many files"; } const parts = singleFileOnly.split(',', 2); yada.showErrorModal(parts[0], parts[1]); return; } return makeAjaxCall(event, $dropTarget, handler); } /** * Remove upload handlers and markers */ function disableDropUpload($element) { $(`[data-yadaDropUpload].${markerDropTarget}`, $element).each(function() { const $dropTarget = $(this); $dropTarget.removeClass(markerDropTarget); $dropTarget.off('dragenter dragover', null, handleDragenterDragover); $dropTarget.off('dragleave dragend drop', null, handleDragleaveDragend); // Is "drop" needed here? if (handleDropProxy!=null) { $dropTarget.off('drop', null, handleDropProxy); } }); } /** * Returns true if the current input key is listed in the data-yadaAjaxTriggerKeys attibute. * Also returns true if the current event is not a key event (like the input event) * Example: yada:ajaxTriggerKeys="Enter| |," * @param inputEvent the input event that has been triggered * return true if the key that triggered the event is listed in * the "data-yadaAjaxTriggerKeys" when present or if that attribute is not present */ yada.isAjaxTriggerKey = function(keyEvent) { const key = keyEvent.key; // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values if (key==null) { return true; // Not a key event so we can't check the key } const input = keyEvent.target; const $input = $(input); const ajaxTriggerKeys = $input.attr("data-yadaAjaxTriggerKeys"); if (ajaxTriggerKeys==null) { return true; // Do ajax call on any key when no attribute present } const triggerKeys = ajaxTriggerKeys.split("|"); for (var i=0; i fields that fire the "keyup" event. * There is no need to pass any element because it is always registered even on dynamically added content. */ yada.enableAjaxInputs = function() { if (this.enableAjaxInputsDone) { // Prevent binding multiple event handlers because yada.enableAjaxInputs is called after each ajax call for legacy reasons return; } // All input fields that are either yadaAjax or data-yadaHref get handled, if they are not radio var selector = "input.yadaAjax:not([type=radio]), input[data-yadaHref]:not([type=radio])"; $(document).on("keyup", selector, function(e) { // If "data-yadaAjaxTriggerKeys" is present, call ajax only when one of the keys is pressed. // If attribute not present, always call ajax if (yada.isAjaxTriggerKey(e)) { const $input = $(this); // Ajax calls are not executed immediately, but after a timeout that is reset each time a valid key is pressed yada.dequeueFunctionCall(this, function(){ makeAjaxCall(e, $input, null, true); }); } }); // Prevent form submission on Enter otherwise the ajax call is not made. // Browsers simulate a click on submit buttons when the enter key is pressed in a form, so we check using the "yadaDoNotSubmitNow" flag. // This doesn't always work and may be necessary to replace submit buttons with normal buttons to prevent form submission on enter. $(selector).each(function(){ const $input = $(this); // Form submission by Enter keypress is allowed when the input element ajax call is not triggered by "Enter". // This only happens if yadaAjaxTriggerKeys is present and does not contain "Enter" const ajaxTriggerKeys = $input.attr("data-yadaAjaxTriggerKeys"); if (ajaxTriggerKeys==null || yada.stringContains(ajaxTriggerKeys, "Enter")) { const $form = $input.closest("form").not(".yadaEnterNoSubmit"); $form.addClass("yadaEnterNoSubmit"); $form.on("submit", function(e){ // The "yadaDoNotSubmitNow" flag is added when the Enter key is pressed in any input element // that does not call ajax when pressing Enter const preventSubmit = $form.data("yadaDoNotSubmitNow")==true; if (preventSubmit) { // e.stopImmediatePropagation(); e.preventDefault(); // No submit, but exec other handlers $form.data("yadaDoNotSubmitNow", false); yada.log("Form submission prevented"); if (ajaxCounter<1) { yada.loaderOff(); } // return false; } }); $form.on("keydown", function(keyEvent){ if (keyEvent.key=="Enter") { const $target = $(keyEvent.target); // The target could be any control in the form, also non-ajax inputs if (!$target.hasClass("yadaAjax") && $target.attr("data-yadaHref")==null) { return; // Non-ajax element can trigger submit } // Prevent submission depending on value of yadaAjaxTriggerKeys, but only if there is a submit control const wouldSubmit = $("[type=submit]:enabled", $form).length>0; if (!wouldSubmit) { // The enter key would not cause a submit, so keep going normally return; } const targetAjaxTriggerKeys = $target.attr("data-yadaAjaxTriggerKeys"); if (targetAjaxTriggerKeys==null || yada.stringContains(targetAjaxTriggerKeys, "Enter")) { $form.data("yadaDoNotSubmitNow", true); // Let the ajax call on the input element run } } }); } }); // Radio buttons that do not use keyup selector = "input.yadaAjax[type=radio], input[data-yadaHref][type=radio]"; $(document).on("input", selector, function(e) { const $input = $(this); makeAjaxCall(e, $input, null, true); }); this.enableAjaxInputsDone = true; $(selector).addClass(markerClass); // Not really needed }; /** * Make an ajax call when a link is clicked, a select is chosen, a checkbox is selected etc. * @param e the triggering event, can be null (for yadaTriggerInViewport) * @param $element the jQuery element that triggered the ajax call * @param optional additional handler to call on success * @param allowDefault true to allow the default event action, if any */ function makeAjaxCall(e, $element, handler, allowDefault) { if (e && !allowDefault==true) { e.preventDefault(); } if ($element.hasClass("yadaAjaxDisabled")) { return false; } // Call, in sequence, the handler specified in data-successHandler and the one passed to this function var joinedHandler = function(responseText, responseHtml) { showFeedbackIfNeeded($element); deleteOnSuccess($element); responseHtml = updateOnSuccess($element, responseHtml); // This removes the added root
// No: Put the responseHtml back into a div if it is not an array and not the original yadaAjaxResponseHtml // Can't be done because the html is removed from the page on append() // if (!(responseHtml instanceof Array) && responseHtml.attr("class")!="yadaAjaxResponseHtml") { // responseHtml = $(yadaAjaxResponseHtmlRoot).append(responseHtml); // } // responseHtml = appendOnSuccess($element, responseHtml); var handlerNames = $element.attr("data-yadaSuccessHandler"); if (handlerNames===undefined) { handlerNames = $element.attr("data-successHandler"); // Legacy } if (handlerNames!=null) { // Can be a comma-separated list of handlers, which are called in sequence yada.executeFunctionListByName(handlerNames, $element, responseText, responseHtml, $element[0]); } if (handler != null) { handler(responseText, responseHtml, $element[0]); } } var data = []; var multipart = false; var method = null; // Defaults to GET var url = null; const droppedFiles = e?.originalEvent?.dataTransfer?.files; if (droppedFiles) { // File drop events use a specific URL that has priority url = $element.attr('data-yadaDropUpload'); // Compile the data object multipart = true; data = new FormData(); for (let i = 0; i < droppedFiles.length; i++) { let file = droppedFiles[i]; data.append('multipartFile', file); } } if (url==null || url=='') { url = $element.attr('data-yadaHref'); } if (url==null || url=='') { url = $element.attr('href'); } if (url==null || url=='') { yada.log("No url for ajax call"); return false; } // Execute submit handlers if any if (!execSubmitHandlers($element)) { return false; } var confirmText = $element.attr("data-yadaConfirm") || $element.attr("data-confirm"); // Create data for submission var value = []; var noLoader = hasNoLoader($element); // In a select, set the data object to the selected option if ($element.is("select")) { $("option:selected", $element).each(function(){ // Could be a multiselect! value.push($(this).val()); // $(this) is correct here }); } else if ($element.is("input")) { if ($element.prop('type')=="checkbox") { value.push($element.prop('checked')); // Always send the element value } else { value.push($element.val()); } } // Add form data when specified with yadaFormGroup const yadaFormGroup = $element.attr('data-yadaFormGroup'); if (yadaFormGroup!=null) { // Find all forms of the same group const $formGroup = $('form[data-yadaFormGroup='+yadaFormGroup+']'); if ($formGroup.length>0) { multipart = $formGroup.filter("[enctype='multipart/form-data']").length > 0; data = multipart ? new FormData() : []; addAllFormsInGroup($formGroup, data); } } // Any yadaRequestData is also sent (see yada.dialect.js) const yadaRequestData = $element[0].yadaRequestData; // Object with name=value data = mergeData(data, yadaRequestData); // Add element value if (value.length>0) { const name = $element.attr("name") || "value"; // Parameter name fallback to "value" by default const toAdd = {}; toAdd.name = name; toAdd.value = value; data = mergeData(data, toAdd); } if (!multipart) { data = $.param(data); } else { method="POST"; } // if (confirmText!=null && confirmText!="") { var title = $element.attr("data-yadaTitle"); var okButton = $element.attr("data-yadaOkButton") || $element.attr("data-okButton") || yada.messages.confirmButtons.ok; var cancelButton = $element.attr("data-yadaCancelButton") || $element.attr("data-cancelButton") || yada.messages.confirmButtons.cancel; var okShowsPreviousModal = $element.attr("data-yadaOkShowsPrevious")==null || $element.attr("data-yadaOkShowsPrevious")=="true"; yada.confirm(title, confirmText, function(result) { if (result==true) { yada.ajax(url, data, joinedHandler==null?joinedHandler:joinedHandler.bind($element), method, getTimeoutValue($element), noLoader); } }, okButton, cancelButton, okShowsPreviousModal); } else { yada.ajax(url, data, joinedHandler==null?joinedHandler:joinedHandler.bind($element), method, null, noLoader); } return true; // Run other listeners } function getTimeoutValue($element) { var timeout = $element.attr('data-yadaTimeout'); if (timeout==null) { timeout = $element.attr('data-timeout'); // Legacy } return timeout; } /** * * @param $element the link or the form * @returns true if "data-yadaDeleteOnSuccess" was present */ function deleteOnSuccess($element) { // Delete a (parent) element // The target can be a parent when the css selector starts with parentSelector (currently "yadaParents:"). // The selector can be multiple, separated by comma. var deleteSelector = $element.attr("data-yadaDeleteOnSuccess"); if (deleteSelector != null) { var selectors = deleteSelector.split(','); // If we delete the $element first, then any following selected elements may not match when relative to the $element. // We therefore first get all selected element then delete them. const toDelete = []; for (var count=0; count1) { // yadaFragment is used only when there is more than one selector, otherwise the whole result is used for replacement $replacementArray = $(".yadaFragment", responseHtml); if ($replacementArray.length==0) { $replacementArray = $("._yadaReplacement_", responseHtml); // Legacy } } if ($replacementArray!=null && $replacementArray.length>1) { $return = []; } var fragmentCount = 0; var focused = false; for (var count=0; count0) { // Clone so that the original responseHtml is not removed by replaceWith. // All handlers are also cloned. $replacement = $replacementArray.eq(fragmentCount).clone(true, true); if (count==0 && $replacementArray.length==1) { $return = $replacement; } else { $return.push($replacement); } // When there are more selectors than fragments, fragments are cycled from the first one fragmentCount = (fragmentCount+1) % $replacementArray.length; } yada.extendedSelect($element, selector).replaceWith($replacement); if (!focused) { // Focus on the first result element with data-yadaAjaxResultFocus const $toFocus = $("[data-yadaAjaxResultFocus]:not([readonly]):not([disabled])", $replacement); if ($toFocus.length>0) { $toFocus.get(0).focus(); focused=true; } } } return $return; */ } /** * * @param $element the link or the form * @param responseHtml jquery object received from the ajax call * @returns the jQuery HTML that has been added to the page, which will be a clone * of responseHtml or the original responseHtml when no update has been made. * In case of multiple appends, an array will be returned. * @deprecated use $append() in the selector instead */ /* function appendOnSuccess($element, responseHtml) { // If "yadaAppendOnSuccess" is set, append to its target; if it's empty, append to the original element. // The target can be a parent when the css selector starts with parentSelector (currently "yadaParents:"). // The selector can be multiple, separated by comma. The appended HTML can be multiplmarkerDropTargetby yadaFragment return postprocessOnSuccess($element, responseHtml, "data-yadaAppendOnSuccess", $.fn.append); } */ /** * This function performs either an update or an append (or more in the future) depending on the parameters. */ function postprocessOnSuccess($element, responseHtml, attributeName, jqueryFunction) { var selector = $element.attr(attributeName); if (selector == null) { return responseHtml; } // Clone so that the original responseHtml is not removed by appending. // All handlers are also cloned. var $replacement = responseHtml.children().clone(true, true); // Uso .children() per skippare il primo div inserito da yada.ajax() // drop events are not cloned properly in jquery 3.7.1 so I reset and reapply the handlers disableDropUpload($replacement); yada.enableDropUpload(null, $replacement); // initObservers($replacement); var $return = $replacement; var selectors = selector.split(','); var $replacementArray = null; // Handle multiple selectors in the update/append attribute if (selectors.length>1) { // yadaFragment is used only when there is more than one selector, otherwise the whole result is used for replacement $replacementArray = $(".yadaFragment", responseHtml); if ($replacementArray.length==0) { $replacementArray = $("._yadaReplacement_", responseHtml); // Legacy } } if ($replacementArray!=null && $replacementArray.length>1) { $return = []; } // var fragmentCount = 0; var focused = false; for (var count=0; count0) { // Clone so that the original responseHtml is not removed by replaceWith. // All handlers are also cloned. $replacement = $replacementArray.eq(fragmentCount).clone(true, true); initObservers($replacement); if (count==0 && $replacementArray.length==1) { $return = $replacement; } else { $return.push($replacement); } // When there are more selectors than fragments, fragments are cycled from the first one fragmentCount = (fragmentCount+1) % $replacementArray.length; } // Detect the jquery funcion used in the selector, if any var jqueryFunction = $.fn.replaceWith; // Default var isReplace = true; var jqueryFunctions = [ {"jqfunction": $.fn.replaceWith, "prefix": "$replaceWith"}, {"jqfunction": $.fn.replaceWith, "prefix": "$replace"}, // $replace() is an alias for $replaceWith() {"jqfunction": $.fn.append, "prefix": "$append"}, {"jqfunction": $.fn.prepend, "prefix": "$prepend"} // More can be added ] for (var i = 0; i < jqueryFunctions.length; i++) { const toCheck = jqueryFunctions[i]; if (yada.startsWith(selector, toCheck.prefix + "(") && selector.indexOf(")") > toCheck.prefix.length) { jqueryFunction = toCheck.jqfunction; selector = yada.extract(selector, toCheck.prefix + "(", ")"); if (!yada.startsWith(toCheck.prefix, "$replace")) { isReplace = false; // Not a replace function } break; } } // Call the jquery function jqueryFunction.call(yada.extendedSelect($element, selector), $replacement); if (isReplace && (selector == null || selector.trim()=="")) { // The original element has been replaced so we need to change it or following selectors won't work anymore $element = $replacement; } if (!focused) { // Focus on the first result element with data-yadaAjaxResultFocus const $toFocus = $("[data-yadaAjaxResultFocus]:not([readonly]):not([disabled])", $replacement); if ($toFocus.length>0) { $toFocus.get(0).focus(); focused=true; } } } return $return; } /** * Show a checkmark fading in and out * @param $element * @returns */ function showFeedbackIfNeeded($element) { var showFeedback = $element.attr("data-yadaShowAjaxFeedback"); if (showFeedback!=undefined) { yada.showAjaxFeedback(); } } /** * Show a checkmark fading in and out, to be called in an ajax success handler when yada:showAjaxFeedback can't be used */ yada.showAjaxFeedback = function() { // Check if the HTML is in page already, else insert it const $feedbackElement = $("#yadaAjaxFeedback"); if ($feedbackElement.length==0) { $("body").append("
"); } $("#yadaAjaxFeedback").fadeIn(200, function() { $("#yadaAjaxFeedback").fadeOut(800); }); } /** * Transform forms into ajax forms: all forms with a class of "yadaAjax" will be sent via ajax. * @param handler a function to call upon successful form submission. It can also be specified as a data-successHandler attribute on the form * @param $element the element on which to enable ajax forms, can be null for the entire body */ yada.enableAjaxForms = function(handler, $element) { if ($element==null || $element=="") { $element = $('body'); } var $target = $element.parent(); if ($target.length==0) { $target = $element; } // This needs to be done every time because the ajax result might have new submit buttons inside, without a form wrapping them yada.enableSubmitButtons($target); // $('form.yadaAjax', $target).each(function() { $(this).removeClass('yadaAjax'); yada.enableAjaxForm($(this), handler); }); // Legacy $('.s_ajaxForm', $target).each(function() { $(this).removeClass('s_ajaxForm'); yada.enableAjaxForm($(this), handler); }); // TODO questo aggiunge la posizione verticale del modal, ma non credo serva per un form ajax // beforeSubmit: function(formDataArray) { // // Aggiunge la posizione verticale del modal tra i parametri del form // formDataArray.push({name: 'ajaxModalScrollTop', value: Math.round($("#ajaxModal").scrollTop())}) // ldm.loaderOn(); // }, }; yada.enableSubmitButtons = function($element) { $element.find("button[type='submit']").not('.yadaClickedButtonHandler').each(function() { $(this).click(function() { clickedButton = this; }); $(this).addClass('yadaClickedButtonHandler'); }); } /** * Disable the ajax submission of a form that was previously initialised as an ajax form * @param $form the jquery form (could be an array), e.g. $('.niceForm') */ yada.disableAjaxForm = function($form) { $form.off("submit"); $form.removeClass(markerClass); } /** * Execute any comma-separated list of sumbit handlers. Each can also be an inline function (with or without function(){} declaration). * Execution stops after the first handler that returns false. */ function execSubmitHandlers($element) { // Invoke any submit handlers either on form, submit button or any ajax-enabled element var submitHandlerNames = $element.attr("data-yadaSubmitHandler"); const result = yada.executeFunctionListByName(submitHandlerNames, $element); if (result!=true) { return false; // Do not send the form } return true; } /** * Merge some new data into an existing object * @param data the object that receives the new data, can be either Array of objects or FormData * @param mergeFrom the object that contains new data like {product: "shoe", quantity: 2}, or null * @return the 'data' object with new data */ function mergeData(data, mergeFrom) { if (mergeFrom==null) { return data; } const multipart = data instanceof FormData; if (!multipart && !(data instanceof Array)) { console.error("YadaError: data should be Array or FormData in mergeData().") return data; } Object.keys(mergeFrom).forEach(function(name) { const value = mergeFrom[name]; if (multipart) { data.set(name, value); // Add data with no duplicates, overwriting previous } else { // Add data with no duplicates, overwriting previous const obj = {}; obj[name] = value; $.extend(true, data, [obj]); } }); return data; } /** * Adds to data all fields in all the forms in the group, optionally excluding one of them * @param $formGroup an array of jquery forms from which input data should be gathered * @param data FormData or an array of objects (created with $.serializeArray()) that may already hold some form data and will contain all the gathered data * @param $form some jquery form to exclude (optional) */ function addAllFormsInGroup($formGroup, data, $formToExclude) { const multipart = data instanceof FormData; if (!multipart && !(data instanceof Array)) { console.error("YadaError: data should be Array or FormData in addAllFormsInGroup().") return; } $formGroup.each(function() { var $eachForm = $(this); if (!$eachForm.is($formToExclude)) { if (multipart) { var eachFormdata = new FormData(this); // Can't use for - of with the current minifyjs version, so trying with a while loop // for (var pair of eachFormdata.entries()) { // data.append(pair[0], pair[1]); // } var iterator = eachFormdata.entries(); var iterElem = iterator.next(); while ( ! iterElem.done ) { var pair = iterElem.value; // const newData = {}; // newData[pair[0]] = pair[1]; // data = mergeData(data, newData); // Add data with no duplicates, keeping first value data.set(pair[0], pair[1]); // Add data with no duplicates, overwriting previous iterElem = iterator.next(); } } else { // mergeData(data, $eachForm.serializeArray()); // Add data with no duplicates, keeping first value $.extend(true, data, $eachForm.serializeArray()); // Add data with no duplicates, overwriting previous } } }); } /** * Sends a form via ajax, it doesn't have to have class .yadaAjax. * @param $form the jquery form (could be an array), e.g. $('.niceForm') * @param successHandler funzione chiamata in caso di successo e nessun yadaWebUtil.modalError() */ yada.enableAjaxForm = function($form, handler) { // If array, recurse to unroll if ($form.length>1) { $form.each(function() { yada.enableAjaxForm($(this), handler); }); return; } yada.enableSubmitButtons($form); // From here on the $form is a single anchor, not an array. // Can't use document.activeElement to find the clicked button because of the possible "confirm" dialog // http://stackoverflow.com/a/33882987/587641 // $form.find("button[type='submit']").not('.yadaClickedButtonHandler').each(function() { // $(this).click(function() { // clickedButton = this; // }); // $(this).addClass('yadaClickedButtonHandler'); // }); // Set the confirm handlers on the form itself if no button has it $form.filter('[data-yadaConfirm]').not('.'+markerClass).each(function() { var $thisForm = $(this); // Check if there is no submit button with a data-yadaConfirm attribute var $button = $thisForm.find('button[type="submit"][data-yadaConfirm]'); if ($button.length==0) { $thisForm.submit(function(e){ if ($thisForm[0]['yadaConfirmed']==true) { $thisForm[0]['yadaConfirmed']=false; return; // Continue submission } $thisForm = $(this); // just in case var confirmText = $thisForm.attr("data-yadaConfirm"); if (confirmText!=null && confirmText!="") { e.preventDefault(); // Stop form submission var title = $button.attr("data-yadaTitle"); var okButton = $button.attr("data-yadaOkButton") || $button.attr("data-okButton") || yada.messages.confirmButtons.ok; var cancelButton = $button.attr("data-yadaCancelButton") || $button.attr("data-cancelButton") || yada.messages.confirmButtons.cancel; yada.confirm(title, confirmText, function(result) { if (result==true) { $thisForm[0]['yadaConfirmed']=true; $thisForm.submit(); } }, okButton, cancelButton); return false; }; }); } }); $form.not('.'+markerClass).submit(function(e) { // Only ajax forms enter here var $form=$(this); // Needed to overwrite the outside variable with the current form, otherwise we may handle the wrong form (because of cloning) // Invoke any submit handlers if (execSubmitHandlers($form)==false) { return false; // Do not send the form } // if (e.isDefaultPrevented()) { return; // Do not send the form - probably a cancel has been made } // // Form alias: submit another form after merging the current form children // var formAliasSelector = $form.attr('data-yadaFormAlias'); // if (formAliasSelector!=null) { // var $formAliasArray = $(formAliasSelector); // if ($formAliasArray!=null && $formAliasArray.length>0) { // var formAlias = $formAliasArray[0]; // if (formAlias.nodeName.toLowerCase()=="form") { // // Replace the current form with a new form composed of all input elements // var $newform=$(formAlias).clone(true); // $form.children().appendTo($newform); // $form = $newform; // Work on the merged form // } // } // } var $formGroup = $form; // Check if this form belongs to a yadaFormGroup var yadaFormGroup = $form.attr('data-yadaFormGroup'); if (yadaFormGroup!=null) { // Find all forms of the same group $formGroup = $('form[data-yadaFormGroup='+yadaFormGroup+']'); } // If the form is marked as markerAjaxButtonOnly do not submit it via ajax unless the clicked button is marked with 'yadaAjax' if ($form.hasClass(markerAjaxButtonOnly)) { if (clickedButton==null || !$(clickedButton).hasClass('yadaAjax')) { yada.addFormGroupFields($form, $formGroup); // Non-ajax submit // In any case, let it continue with the submit return; // Do a normal submit } } e.preventDefault(); // From now on the form can only be ajax-submitted var noLoader = hasNoLoader($form); var action = $(this).attr('action'); // Check if it must be a multipart formdata var multipart = $form.attr("enctype")=="multipart/form-data"; // If CKEditor is being used, ensure all fields are valid (copies from ck-managed-fields to normal form fields) $(".ck-editor__editable").each(function(){ this.ckeditorInstance.updateSourceElement(); }); // Using FormData to send files too: https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData var data = multipart ? new FormData(this) : $(this).serializeArray(); // Add data from the form group if any if ($formGroup.length>1) { addAllFormsInGroup($formGroup, data, $form); } // Add data from any child form recursively, if any var $childForm = $form[0]['yadaChildForm']; while ($childForm != null) { if (multipart) { var childFormData = new FormData($childForm); // Can't use for - of with the current minifyjs version, so trying with a while loop // for (var pair of eachFormdata.entries()) { // data.append(pair[0], pair[1]); // } var iterator = childFormData.entries(); var iterElem = iterator.next(); while ( ! iterElem.done ) { var pair = iterElem.value; data.append(pair[0], pair[1]); iterElem = iterator.next(); } } else { $.merge(data, $childForm.serializeArray()); } $childForm = $childForm[0]['yadaChildForm']; } var buttonName = null; var buttonValue = null; var buttonHistoryAttribute = false; if (clickedButton!=null) { // Invoke any submit handlers if (execSubmitHandlers($(clickedButton))==false) { return false; // Do not send the form } // buttonName = $(clickedButton).attr("name"); buttonValue = $(clickedButton).attr("value") || ""; if (multipart && buttonName!=null && !data.has(buttonName)) { data.append(buttonName, buttonValue); } else if (!multipart && buttonName!=null && data[buttonName]==null) { data.push({name: buttonName, value: buttonValue}); } var buttonAction = $(clickedButton).attr("formaction"); if (buttonAction!=null) { action = buttonAction; } // Either the form or the button can have a noLoader flag noLoader |= hasNoLoader($(clickedButton)); // Pagination history buttonHistoryAttribute = handlePaginationHistoryAttribute($(clickedButton), $(clickedButton).closest("form")); } if (!multipart) { data = $.param(data); } // Call, in sequence, the handler specified in data-successHandler and the one passed to this function. // Extend the handler to include form and button parameters var localClickedButton = clickedButton; // Create a closure otherwise the clicked button is lost var joinedHandler = function(responseText, responseHtml) { showFeedbackIfNeeded($form); if ($(localClickedButton).attr("data-yadaUpdateOnSuccess")!=null) { responseHtml = updateOnSuccess($(localClickedButton), responseHtml); } else { responseHtml = updateOnSuccess($form, responseHtml); } /* if ($(localClickedButton).attr("data-yadaAppendOnSuccess")!=null) { responseHtml = appendOnSuccess($(localClickedButton), responseHtml); } else { responseHtml = appendOnSuccess($form, responseHtml); } */ var formHandlerNames = $form.attr("data-yadaSuccessHandler"); if (formHandlerNames===undefined) { formHandlerNames = $form.attr("data-successHandler"); // Legacy } // var dataHandler = window[formHandlerName]; var buttonHandlerNames = $(localClickedButton).attr("data-yadaSuccessHandler"); // var buttonDataHandler = window[buttonHandlerName]; // The button handler has precedence over the form handler, which is called only if the former returns true // from all handlers. var runFormHandler = true; if (buttonHandlerNames != null) { // Can be a comma-separated list of handlers, which are called in sequence runFormHandler &= yada.executeFunctionListByName(buttonHandlerNames, $form, responseText, responseHtml, this, localClickedButton); } if (runFormHandler == true && formHandlerNames!=null) { // Can be a comma-separated list of handlers, which are called in sequence yada.executeFunctionListByName(formHandlerNames, $form, responseText, responseHtml, this, localClickedButton); } if (handler != null) { handler(responseText, responseHtml, this, localClickedButton); } // Deletion must be done later or it could prevent other actions from working when the target is self var deleted = deleteOnSuccess($(localClickedButton)); if (!deleted) { deleteOnSuccess($form); } }; var method = $form.attr('method') || "POST"; if (!buttonHistoryAttribute) { handlePaginationHistoryAttribute($form, $form); } yada.ajax(action, data, joinedHandler.bind(this), method, getTimeoutValue($form), noLoader); clickedButton = null; return false; // Important so that the form is not submitted by the browser too }) // submit() // Set the confirm handlers on form buttons $form.not('.'+markerClass).find("button[type='submit']").each(function() { var $button = $(this); var confirmText = $button.attr("data-yadaConfirm") || $button.attr("data-confirm"); if (confirmText!=null && confirmText!="") { var title = $button.attr("data-yadaTitle"); var okButton = $button.attr("data-yadaOkButton") || $button.attr("data-okButton") || yada.messages.confirmButtons.ok; var cancelButton = $button.attr("data-yadaCancelButton") || $button.attr("data-cancelButton") || yada.messages.confirmButtons.cancel; $button.click(function() { $button = $(this); // Needed otherwise $button could be stale (from a previous ajax replacement) yada.confirm(title, confirmText, function(result) { if (result==true) { $button.off("click"); $button.click(); // No $form.submit(); because of the button name that has to be preserved (see clickedButton above) // TODO/BUG if the submit button contains an , that value will not be included in $(form).serializeArray() and will not be sent // This only happens when the form is sent with $button.click(): its value is sent correctly when no confirm dialog is used. // The workaround is to set the value of on the submit button itself using name= and value= attributes } }, okButton, cancelButton); return false; // Stop form submission }); } }); $form.not('.'+markerClass).addClass(markerClass); }; function showFullPage(html) { document.open(); document.write(html); document.close(); } /** * Download ajax-received data */ yada.downloadData = function(data, filename, mimeType) { /*if (filename==null || filename.trim()=="") { filename = "" }*/ var blob = new Blob([data], {type : mimeType}); yada.downloadBlob(blob, filename); } /** * Download ajax-received data */ yada.downloadBlob = function(blob, filename) { var link = document.createElement("a"); link.href = URL.createObjectURL(blob); link.download = filename; link.style="display: none;"; document.body.appendChild(link); link.click(); } /** * Esegue una get/post ajax passando data (stringa od oggetto). Gestisce il caso che sia necessario il login. * Il metodo chiamato lato java può ritornare un notify chiamando yadaWebUtil.modalOk() o anche yadaWebUtil.modalError() etc. * In caso di notify di un errore, l'handler non viene chiamato. * @param url target url * @param data data to send, string or object. Can be null. Json objects are converted with JSON.stringify and given a specific content-type. * To send a multipart request, data must be a FormData. * @param successHandler(responseText, responseHtml);) funzione chiamata in caso di successo e nessun yadaWebUtil.modalError(). Viene chiamata anche in caso di errore se il suo flag executeAnyway è true * @param method "POST" per il post oppure null o "GET" per il get * @param timeout milliseconds timeout, null for default (set by the browser) * @param hideLoader true for not showing the loader * @param asJson true to send a JSON.stringify(data) with "application/json;charset=UTF-8" * @param responseType set the response type, for example "blob" for downloading binary data (e.g. a pdf file) */ yada.ajax = function(url, data, successHandler, method, timeout, hideLoader, asJson, responseType) { if (successHandler=="GET" || successHandler=="POST") { console.error("YadaError: you are forgetting the successHandler in the yada.ajax call; use null for no handler.") return; } if (method==null) { method="GET" } if (timeout==null) { timeout=0; // Default } var processData = !(data instanceof FormData); // http://stackoverflow.com/a/8244082/587641 var contentType = undefined; if (asJson==true) { processData = false; contentType = "application/json;charset=UTF-8"; data = JSON.stringify(data); } else { contentType = data instanceof FormData ? false : contentType; } if (hideLoader==true) { yada.loaderOff(); } else { yada.loaderOn(); } var xhrFields = {}; if (responseType!=null) { xhrFields.responseType = responseType; } // Call the server ajaxCounter++; $.ajax({ type: method, url: url, data: data, processData: processData, contentType: contentType, xhrFields: xhrFields, error: function(jqXHR, textStatus, errorThrown ) { ajaxCounter--; if (ajaxCounter<1) { yada.loaderOff(); } // textStatus is "error", "timeout", "abort", or"parsererror" var responseText = jqXHR.responseText!= null ? jqXHR.responseText.trim() : jqXHR.responseText; if (jqXHR.status==503 && responseText!=null && yada.startsWith(responseText, " so that $find() works when multiple root elements are returned. // - not sure it is a good idea but legacy code needs it now. // The bad thing is that the enclosing div is stripped when updateOnSuccess is called, so the successHandler // can receive both versions (with or without root div) depending on the presence of the updateOnSuccess call. // The reason for stripping it is that "replaceWith" and other successHandler functions move the children from the // added top
element, so it can't be returned anyway because it would be empty. var responseHtml=$(yadaAjaxResponseHtmlRoot).html(responseTrimmed); // const getOut = doDeprecatedStuff(responseTrimmed, responseHtml, responseText, url, data, successHandler, method); if (getOut) { return; } // // Se è stato ritornato un confirm, lo mostro e abilito l'esecuzione dell'ajax e dell'handler if (yada.handleModalConfirm(responseHtml, url, data, successHandler, method)) { if (ajaxCounter<1) { yada.loaderOff(); } return; } // Always initialize all handlers on the returned content yada.initHandlersOn(responseHtml); // Il successHandler viene eseguito solo se non c'è un errore, oppure se il flag executeAnyway è true if (successHandler != null) { if (!yada.isNotifyError(responseHtml) || successHandler.executeAnyway==true) { // yada.initAjaxHandlersOn(responseHtml); // Non c'era un login, eseguo l'handler, se passato successHandler(responseText, responseHtml); // Keep going... } } // If it is a full page, overwrite the current one. The class .yadafullPage must not be on the body. // No: The result is a full page if it has " -1; if ($('.yadafullPage', responseHtml).length>0 || $('.s_fullPage', responseHtml).length>0) { showFullPage(responseText); if (ajaxCounter<1) { yada.loaderOff(); } return; } // Per mostrare una notification al ritorno dalla get, basta che il Controller ritorni "/yada/modalNotify" // dopo aver chiamato ad esempio yadaWebUtil.modalOk() var notify=yada.handleNotify(responseHtml); if (notify) { return; } // Open any other modal, excluding any embedded confirm modal var $loadedModalDialog=$(responseHtml).find(".modal > .modal-dialog").first(); if ($loadedModalDialog.length==1) { handleAjaxLoadedModal($loadedModalDialog, responseHtml, responseText); return; } // If the result is "closeModal", close all open modals if (responseTrimmed == 'closeModal') { $(".modal:visible").modal('hide'); } if (ajaxCounter<1) { yada.loaderOff(); } // End of ajax success }, timeout: yada.devMode?0:timeout, traditional: true, // Serve per non avere id[] : '12' ma id : '12' xhr: function() { // Changes the bootstrap progress bar width $(".loader .progress-bar").css("width", 0); var xhr = $.ajaxSettings.xhr() ; xhr.upload.onprogress = function(evt){ $(".loader .progress-bar").css("width", evt.loaded/evt.total*100+"%"); } ; // xhr.upload.onload = function(){ console.log('DONE!') } ; return xhr ; } }); } /** * Handles ajax redirects * @param {*} responseTrimmed * @returns true if the redirect opened a new tab */ function ajaxRedirect(responseTrimmed) { var redirectObject = JSON.parse(responseTrimmed); // Get the redirect url and remove any "redirect:" prefix from the url var targetUrl = yada.getAfter(redirectObject.redirect, "redirect:"); if (redirectObject.newTab!="true") { const currentServer = window.location.origin; // https://www.example.com:8080 const redirectServer = yada.getServerAddress(targetUrl); const currentPathSearch = window.location.pathname + window.location.search; const redirectPathSearch = yada.removeHash(targetUrl); const currentHashValue = yada.getHashValue(window.location.hash); // '' or 'value' const redirectHashValue = yada.getHashValue(targetUrl); window.location.href=targetUrl; // When only the #anchor changes between current and new url, browsers // might not reload the page so we force a reload if (currentServer==redirectServer || redirectServer=='') { if (currentPathSearch==redirectPathSearch) { // Automatic reloading only happens when there are no hashes // or when the current hash is removed. // So we force a reload only when a hash is added/modified. if (redirectHashValue!='') { window.location.reload(true); } } } return false; // Needed to prevent flashing of the loader } else { if (ajaxCounter<1) { yada.loaderOff(); } var win = window.open(targetUrl, '_blank'); if (win) { // Browser has allowed it to be opened win.focus(); } else { // Browser has blocked it alert('Please allow popups for this website'); } } return true; } function handleAjaxLoadedModal($loadedModalDialog, responseHtml, responseText) { $("#loginModal").remove(); // TODO still needed? // A modal was returned. Is it a "sticky" modal? var stickyModal = $loadedModalDialog.hasClass(yada.stickyModalMarker); // Remove any currently downloaded modals (markerAjaxModal) if they are open and not sticky var $existingModals = $(".modal.show."+markerAjaxModal+":has(.modal-dialog:not(."+yada.stickyModalMarker+"))"); if ($existingModals.length==0) { // Try Bootstrap 3 selector $existingModals = $(".modal.in."+markerAjaxModal+":has(.modal-dialog:not(."+yada.stickyModalMarker+"))"); } if ($existingModals.length>0) { $existingModals.modal("hide"); // Remove the background too // $existingModals.remove(); // This prevents removal of the modal background sometimes $existingModals.on('hidden.bs.modal', function (e) { $existingModals.remove(); // Remove the existing modal after it's been closed }); } // modals are appended to the body const $modalObject = $(responseHtml).find(".modal").first(); // Add the marker class $modalObject.addClass(markerAjaxModal); if (stickyModal) { // This container is needed to keep the scrollbar when a second modal is closed var $container = $(""); $container.append($modalObject); $("body").prepend($container); $modalObject.on('hidden.bs.modal', function (e) { $container.remove(); // Remove modal on close }); } else { $("body").prepend($modalObject); $modalObject.on('hidden.bs.modal', function (e) { $modalObject.remove(); // Remove modal on close }); } // Adding the modal head elements to the main document if (responseText.indexOf('')>-1) { var parser = new DOMParser(); var htmlDoc = parser.parseFromString(responseText, "text/html"); var headNodes = $(htmlDoc.head).children(); $("head").append(headNodes); removeHeadNodes(headNodes, $modalObject) // Needed a closure for headNodes (?) } // We need to show the modal after a delay or it won't show sometimes (!) var modalIsHidden = !$modalObject.is(':visible'); if (modalIsHidden) { setTimeout(function() { $modalObject.modal('show'); if (stickyModal) { // Need to fix the z-index to allow other modals to show on top and shade it var $background = $(".modal-backdrop.fade.show").last(); var z = $background.css("z-index"); // 1040 $modalObject.css("z-index", z-1); // 1039, must be less than 1040 to be behind a future background $background.css("z-index", z-2); } // The loader is removed after the modal is opened to prevent background flickering (if the loader background is not transparent) $modalObject.on('shown.bs.modal', function (e) { if (ajaxCounter<1) { yada.loaderOff(); } }) }, 100); } else { if (ajaxCounter<1) { yada.loaderOff(); } } // This should not be needed because handlers have already been initialized on all the returned html // yada.initAjaxHandlersOn($modalObject); // Scroll the modal to an optional anchor (delay was needed for it to work) // or scroll back to top when it opens already scrolled (sometimes it happens) setTimeout(function() { var hashValue = window.location.hash; // #234 if (hashValue.length>1 && !isNaN(hashValue.substring(1))) { try { $modalObject.animate({ scrollTop: $(hashValue).offset().top }, 1000); } catch (e) {} } else if ($modalObject.scrollTop()>0) { // Scroll back to top when already scrolled $modalObject.animate({ scrollTop: 0 }, 500); } }, 500); } /** * Performs some deprecated actions that should be removed one day * @param {*} responseTrimmed * @param {*} responseHtml * @param {*} responseText * @param {*} url * @param {*} data * @param {*} successHandler * @param {*} method * @returns true if the ajax method should terminate */ function doDeprecatedStuff(responseTrimmed, responseHtml, responseText, url, data, successHandler, method) { // Deprecated - to be removed: // Check if we just did a login. // A successful login can also return a redirect, which will skip the PostLoginHandler // The "loginSuccess" string is not returned anymore. if ("loginSuccess" == responseTrimmed) { // @Deprecated. Should use the generic modal instead of the login modal $("#loginModal").modal("hide"); yada.loaderOff(); // window.location.reload(true); // true = skip cache // Non va bene perchè se è stata fatta una post, viene ripetuta! yada.handlePostLoginHandler(responseHtml, responseText); return true; } // if (openLoginModalIfPresent(responseHtml)) { // @Deprecated. Should use the generic modal instead of the login modal yada.loaderOff(); return true; } // Controllo se è stata ritornata la home con una richiesta di login if ((typeof responseText == 'string' || responseText instanceof String) && responseText.indexOf('s_loginRequested') !== -1) { // @Deprecated. Should use the generic modal instead of the login modal yada.openLoginModal(url, data, successHandler, method); // E' necessario il login. Viene fatto, e poi eseguito l'handler. yada.loaderOff(); return true; } // Gestisce la pwd scaduta var pwdChange=$(responseHtml).find("body.yadaChangePassword"); if (pwdChange.length>0) { $("#loginModal").remove(); showFullPage(responseText); yada.loaderOff(); return true; } return false; } function removeHeadNodes(headNodes, $modalObject) { $modalObject.on('hidden.bs.modal', function (e) { if (headNodes!=null) { try { headNodes.remove(); // Cleanup on modal close } finally {}; } }); } /** * Se esiste un confirm nel response, lo visualizza e, in caso l'utente confermi, esegue la chiamata originale aggiungendo "confirmed" ai parametri. * WARNING: any modal will be closed and its close-handlers invoked before showing the confirm dialog * @param data can be either a string or an object or null */ yada.handleModalConfirm = function(responseHtml, url, data, successHandler, type) { var $modalConfirm=$(responseHtml).find(".s_modalConfirm .modal"); if ($modalConfirm.length>0) { // Close all non-sticky modals const $currentModals = $(".modal:not(."+yada.stickyModalMarker+"):visible"); // If the modal has been loaded via yada.ajax, on hide it will be removed // so we clone the content in order to keep the event handlers. // Don't find just the ajaxModals or we would complicate the restore section later. const clonedModalContentArray = $currentModals.map(function() { return $(this).find('.modal-content').first().clone(true, true); }).get(); $currentModals.modal('hide'); // Hide any modal that might be already open if ($("#yada-confirm .modal").length==0) { console.error("[yada] No confirm modal found: did you include it?"); } $("#yada-confirm .modal").children().remove(); $("#yada-confirm .modal").append($(".modal-dialog", $modalConfirm)); $("#yada-confirm .modal").modal('show'); $("#yada-confirm .okButton").click(function() { // $("#yada-confirm .okButton").off(); // ?????????? A cosa servono questi postXXXX ?????????????????? postLoginUrl = null; postLoginData = null; yada.postLoginHandler = null; postLoginType = null; if (typeof(data)=='string') { data = yada.addUrlParameterIfMissing(data, "yadaconfirmed", "true", false); } else { if (data==null) { data = {}; } data.yadaconfirmed=true; } yada.ajax(url, data, successHandler, type); }); $("#yada-confirm .cancelButton").click(function() { $('#yada-confirm .modal').one('hidden.bs.modal', function (e) { // $currentModals.modal('show'); $currentModals.each(function(index) { if (isRemoved($(this))) { // The modal was removed with all the nested handlers, so I replace the // content with the cloned one to restore the handlers. $('.modal-content', this).replaceWith(clonedModalContentArray[index]); } $(this).modal('show'); }); $('#yada-confirm .modal').off('hidden.bs.modal'); // Useless? }); // $("#yada-confirm .modal").modal('hide'); }); return true; } return false; } function extractError(responseText) { var errorKeyword = 'yadaError:'; if (typeof responseText === "string") { var trimmedText = responseText.trim(); if (yada.startsWith(trimmedText, errorKeyword)) { return trimmedText.substring(errorKeyword.length); } } return null; } // Apre un errore se il risultato di una chiamata ajax contiene l'oggetto ajaxError o lo stato è diverso da success yada.showAjaxErrorIfPresent = function(responseText, statusText, errorObject) { var errorMessage = null; if (typeof responseText == "object" && responseText.error!=null) { errorMessage = responseText.error; } else if (errorObject!=null && errorObject.yadaError!=null) { errorMessage = errorObject.yadaError.error; } else { errorMessage = extractError(responseText); } if (errorMessage!=null) { yada.showErrorModal("Error", errorMessage!=""?errorMessage:"Generic Error"); return true; } return false; }; // // Chiamato se le chiamate ajax vanno in errore // // TODO deprecato da eliminare? Vedi se lo usa ancora ldm.js // yada.ajaxNetworkError = function(jqXHR, textStatus, errorThrown) { // yada.showErrorModal("Errore (" + errorThrown + ")", "Si è verificato un errore imprevisto. Ricarica la pagina e riprova"); // }; // Ritorna true se nell'html c'è un notify di tipo error yada.isNotifyError = function(responseHtml) { return $('.yadaNotify span.glyphicon.error', responseHtml).not('.hidden').length>0; } // Se un ritorno da una chiamata ajax ha un notify, lo mostra. // Per mostrare un notify al ritorno dalla get, basta che il Controller ritorni "/yada/modalNotify" // dopo aver chiamato ad esempio yadaWebUtil.modalOk() // Ritorna true se la notify è stata mostrata. yada.handleNotify = function(responseHtml) { var notification=$(responseHtml).find(".yadaNotify"); if (notification.length==1) { // Mostro la notification $('.modal:visible').modal('hide'); // Close any current modals $('#yada-notification').children().remove(); $('#yada-notification').append(notification); // We need to show the modal after a delay or it won't show sometimes (!) setTimeout(function() { $('#yada-notification').on('show.bs.modal', function (e) { // Keep the loader open until the modal is fully shown, to prevent "flashing". // This should become a configurable option maybe if (!notification.hasClass("yadaLoaderKeep")) { if (ajaxCounter<1) { yada.loaderOff(); } } }); $('#yada-notification').modal('show'); }, 200); return true; } return false; } /** * Return the data in a table inside a yadaResponseData, or an empty array */ yada.getEmbeddedResult = function(html) { var result = {}; $(".yadaResponseData table tr", html).each(function(){ var key = $(".s_key", this).text(); var value = $(".s_value", this).text(); result[key] = value; }); return result; } }( window.yada = window.yada || {} ));




© 2015 - 2024 Weber Informatics LLC | Privacy Policy