/** * Installer's AJAX frontend handler */ (function($) { // Avoid conflicts with other libraries 'use strict'; // Installer variables var pollTimer = null; var nextReadPosition = 0; var progressBarTriggered = false; var progressTimer = null; var currentProgress = 0; // Template related variables var $contentWrapper = $('.install-body').find('.main'); // Intercept form submits interceptFormSubmit($('#install_install')); /** * Creates an XHR object * * jQuery cannot be used as the response is streamed, and * as of now, jQuery does not provide access to the response until * the connection is not closed. * * @return XMLHttpRequest */ function createXhrObject() { return new XMLHttpRequest(); } /** * Displays error, warning and log messages * * @param type * @param messages */ function addMessage(type, messages) { // Get message containers var $errorContainer = $('#error-container'); var $warningContainer = $('#warning-container'); var $logContainer = $('#log-container'); var $title, $description, $msgElement, arraySize = messages.length; for (var i = 0; i < arraySize; i++) { $msgElement = $('<div />'); $title = $(document.createElement('strong')); $title.text(messages[i].title); $msgElement.append($title); if (messages[i].hasOwnProperty('description')) { $description = $(document.createElement('p')); $description.html(messages[i].description); $msgElement.append($description); } switch (type) { case 'error': $msgElement.addClass('errorbox'); $errorContainer.append($msgElement); break; case 'warning': $msgElement.addClass('warningbox'); $warningContainer.append($msgElement); break; case 'log': $msgElement.addClass('log'); $logContainer.append($msgElement); break; case 'success': $msgElement.addClass('successbox'); $errorContainer.prepend($msgElement); break; } } } /** * Displays a form from the response * * @param formHtml */ function addForm(formHtml) { var $formContainer = $('#content-container'); $formContainer.html(formHtml); var $form = $('#install_install'); interceptFormSubmit($form); } /** * Handles navigation status updates * * @param navObj */ function updateNavbarStatus(navObj) { var navID, $stage, $stageListItem, $active; $active = $('#activemenu'); if (navObj.hasOwnProperty('finished')) { // This should be an Array var navItems = navObj.finished; for (var i = 0; i < navItems.length; i++) { navID = 'installer-stage-' + navItems[i]; $stage = $('#' + navID); $stageListItem = $stage.parent(); if ($active.length && $active.is($stageListItem)) { $active.removeAttr('id'); } $stage.addClass('completed'); } } if (navObj.hasOwnProperty('active')) { navID = 'installer-stage-' + navObj.active; $stage = $('#' + navID); $stageListItem = $stage.parent(); if ($active.length && !$active.is($stageListItem)) { $active.removeAttr('id'); } $stageListItem.attr('id', 'activemenu'); } } /** * Renders progress bar * * @param progressObject */ function setProgress(progressObject) { var $statusText, $progressBar, $progressText, $progressFiller; if (progressObject.task_name.length) { if (!progressBarTriggered) { // Create progress bar var $contentContainer = $('#content-container'); // Create progress bar elements $progressBar = $('<div />'); $progressBar.attr('id', 'progress-bar'); $progressText = $('<p />'); $progressText.attr('id', 'progress-bar-text'); $progressFiller = $('<span />'); $progressFiller.attr('id', 'progress-bar-filler'); $statusText = $('<p />'); $statusText.attr('id', 'progress-status-text'); $progressBar.append($progressFiller); $progressBar.append($progressText); $contentContainer.append($statusText); $contentContainer.append($progressBar); progressBarTriggered = true; } else { $statusText = $('#progress-status-text'); } // Update progress bar $statusText.text(progressObject.task_name + '…'); incrementProgressBar(Math.round(progressObject.task_num / progressObject.task_count * 100)); } } // Set cookies function setCookies(cookies) { var cookie; for (var i = 0; i < cookies.length; i++) { // Set cookie name and value cookie = encodeURIComponent(cookies[i].name) + '=' + encodeURIComponent(cookies[i].value); // Set path cookie += '; path=/'; document.cookie = cookie; } } /** * Parse messages from the response object * * @param messageJSON */ function parseMessage(messageJSON) { $('#loading_indicator').css('display', 'none'); var responseObject = JSON.parse(messageJSON); // Parse object if (responseObject.hasOwnProperty('errors')) { addMessage('error', responseObject.errors); } if (responseObject.hasOwnProperty('warnings')) { addMessage('warning', responseObject.warnings); } if (responseObject.hasOwnProperty('logs')) { addMessage('log', responseObject.logs); } if (responseObject.hasOwnProperty('success')) { addMessage('success', responseObject.success); } if (responseObject.hasOwnProperty('form')) { addForm(responseObject.form); } if (responseObject.hasOwnProperty('progress')) { setProgress(responseObject.progress); } if (responseObject.hasOwnProperty('nav')) { updateNavbarStatus(responseObject.nav); } if (responseObject.hasOwnProperty('cookies')) { setCookies(responseObject.cookies); } } /** * Process updates in streamed response * * @param xhReq XHR object */ function pollContent(xhReq) { var messages = xhReq.responseText; var msgSeparator = '}\n\n'; var unprocessed, messageEndIndex, endOfMessageIndex, message; do { unprocessed = messages.substring(nextReadPosition); messageEndIndex = unprocessed.indexOf(msgSeparator); if (messageEndIndex !== -1) { endOfMessageIndex = messageEndIndex + msgSeparator.length; message = unprocessed.substring(0, endOfMessageIndex); parseMessage(message); nextReadPosition += endOfMessageIndex; } } while (messageEndIndex !== -1); if (xhReq.readyState === 4) { $('#loading_indicator').css('display', 'none'); resetPolling(); } } /** * Animates the progress bar * * @param $progressText * @param $progressFiller * @param progressLimit */ function incrementFiller($progressText, $progressFiller, progressLimit) { if (currentProgress >= progressLimit || currentProgress >= 100) { clearInterval(progressTimer); return; } currentProgress++; $progressText.text(currentProgress + '%'); $progressFiller.css('width', currentProgress + '%'); } /** * Wrapper function for progress bar rendering and animating * * @param progressLimit */ function incrementProgressBar(progressLimit) { var $progressFiller = $('#progress-bar-filler'); var $progressText = $('#progress-bar-text'); var progressStart = $progressFiller.width() / $progressFiller.offsetParent().width() * 100; currentProgress = Math.floor(progressStart); clearInterval(progressTimer); progressTimer = setInterval(function() { incrementFiller($progressText, $progressFiller, progressLimit); }, 10); } /** * Resets the polling timer */ function resetPolling() { clearInterval(pollTimer); nextReadPosition = 0; } /** * Sets up timer for processing the streamed HTTP response * * @param xhReq */ function startPolling(xhReq) { resetPolling(); pollTimer = setInterval(function () { pollContent(xhReq); }, 250); } /** * Renders the AJAX UI layout */ function setupAjaxLayout() { // Clear content $contentWrapper.html(''); var $header = $('<div />'); $header.attr('id', 'header-container'); $contentWrapper.append($header); var $description = $('<div />'); $description.attr('id', 'description-container'); $contentWrapper.append($description); var $errorContainer = $('<div />'); $errorContainer.attr('id', 'error-container'); $contentWrapper.append($errorContainer); var $warningContainer = $('<div />'); $warningContainer.attr('id', 'warning-container'); $contentWrapper.append($warningContainer); var $installerContentWrapper = $('<div />'); $installerContentWrapper.attr('id', 'content-container'); $contentWrapper.append($installerContentWrapper); var $logContainer = $('<div />'); $logContainer.attr('id', 'log-container'); $contentWrapper.append($logContainer); var $spinner = $('<div />'); $spinner.attr('id', 'loading_indicator'); $spinner.html(' '); $contentWrapper.append($spinner); } // Submits a form function submitForm($form, $submitBtn) { $form.css('display', 'none'); var xhReq = createXhrObject(); xhReq.open('POST', $form.attr('action'), true); xhReq.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); xhReq.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); xhReq.send(getFormFields($form, $submitBtn)); // Disable language selector $('#language_selector :input, label').css('display', 'none'); // Clear content setupAjaxLayout(); $('#loading_indicator').css('display', 'block'); startPolling(xhReq); } /** * Add submit button to the POST information * * @param $form * @param $submitBtn * * @returns {*} */ function getFormFields($form, $submitBtn) { var formData = $form.serialize(); formData += ((formData.length) ? '&' : '') + encodeURIComponent($submitBtn.attr('name')) + '='; formData += encodeURIComponent($submitBtn.attr('value')); return formData; } /** * Intercept form submit events and determine the submit button used * * @param $form */ function interceptFormSubmit($form) { if (!$form.length) { return; } $form.find(':submit').bind('click', function (event) { event.preventDefault(); submitForm($form, $(this)); }); } })(jQuery); // Avoid conflicts with other libraries