%PDF- %PDF-
Direktori : /home/lightco1/upgrade.lightco.com.au/administrator/components/com_jmap/js/ |
Current File : /home/lightco1/upgrade.lightco.com.au/administrator/components/com_jmap/js/xmlprecaching.js |
/** * Precaching client, this is the main application that interacts with server * side code for sitemap incremental generation and precaching process * * @package JMAP::AJAXPRECACHING::administrator::components::com_jmap * @subpackage js * @author Joomla! Extensions Store * @copyright (C) 2015 Joomla! Extensions Store * @license GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html */ //'use strict'; (function($) { var XmlPrecaching = function() { /** * Current active data sources for sitemap genaration * The first async call to ajaxserver is mean to grab the full list * of published data sources to process * * @access private * @var Array */ var dataSources; /** * During the recursive async promise callback * this represent the current processed data source id * sent to server for precacher generation * * @access private * @var Int */ var currentProcessedDataSource; /** * Number of links processed for a single data source * during run processing status * * @access private * @var Int */ var currentProcessedLinks; /** * Selected language if any from dropdown * * @access private * @var String */ var selectedLanguage; /** * Status of the process * * @access private * @var String */ var processStatus; /** * Status of the cycle and allowed running mode * If ESC button pressed or process stopped by user * the onCycle var is false and runProcessing is stopped * * @access private * @var String */ var onCycle; /** * Target sitemap links parsed to be used for ajax sitemap generation * * @access private * @var Object */ var targetParsedSitemapLink; /** * Store the clicked target generation button * * @access private * @var Object */ var targetGenerationButton; /** * Iteration counter for recursive promise callback * Server side process the iteration number to go step by step * during sitemap generation * * @access private * @var Int */ var iterationCounter; /** Callbacks container array * * @access private * @var array */ var callbacksContainer; /** * Start buttons for precaching process * * @access private * @var String */ var startButtons = 'label[data-role=startprecaching]'; /** * Snippet for clear button * * @access private * @var String */ var clearCacheButtons = '<button type="button" data-role="clearcache" data-loading-text="' + COM_JMAP_PRECACHING_CLEARING + '" class="btn btn-info btn-mini">' + COM_JMAP_PRECACHING_CLEAR_CACHE + '</button>'; /** * Inline user messages * * @access private * @var String */ var userMessageAlerts = '<div class="alert alert-danger"><span class="glyphicon glyphicon-exclamation-sign"></span><span class="alert-message"></span></div>'; /** * Parse url to grab query string params to post to server side for sitemap generation * * @access private * @return Object */ var parseURL = function(url) { var a = document.createElement('a'); a.href = url; return { source: url, protocol: a.protocol.replace(':',''), host: a.hostname, port: a.port, query: a.search, params: (function(){ var ret = {}, seg = a.search.replace(/^\?/,'').split('&'), len = seg.length, i = 0, s; for (;i<len;i++) { if (!seg[i]) { continue; } s = seg[i].split('='); ret[s[0]] = s[1]; } return ret; })(), file: (a.pathname.match(/\/([^\/?#]+)$/i) || [,''])[1], hash: a.hash.replace('#',''), path: a.pathname.replace(/^([^\/])/,'/$1'), relative: (a.href.match(/tps?:\/\/[^\/]+(.+)/) || [,''])[1], segments: a.pathname.replace(/^\//,'').split('/') }; } /** * Register user events for interface controls * * @access private * @param Boolean initialize * @return Void */ var addListeners = function(initialize) { // Start the precaching process, first operation is enter the progress modal mode $(startButtons).on('click.precaching', function(jqEvent){ showProgress(true, 20, 'standard', COM_JMAP_START_PRECACHING_PROCESS); targetGenerationButton = this; // Grab targeted sitemap link var tempTargetLink = $(this).parent().children('#jmap_seo input[data-role=sitemap_links]').val() || $(this).parent().children('#jmap_seo input[data-role=sitemap_links_sef]').attr('data-valuenosef'); // Reset always sitemap params to merge dynamically targetParsedSitemapLink = { format:null, lang:null, dataset:null, Itemid:null } targetParsedSitemapLink = $.extend(targetParsedSitemapLink, parseURL(tempTargetLink).params); }); // Live event binding only once on initialize, avoid repeated handlers and executed callbacks if(initialize) { // Language options change and Menu filter change $('#language_option, #menu_datasource_filters, #datasets_filters').on('change.precaching', function(jqEvent, isTriggered){ // Check if event is fired by real UI and not jQuery programmatic trigger if(!isTriggered) { setPrecachedStatusLabels(jqEvent); // Set current language if($(this).attr('id') == 'language_option') { selectedLanguage = '/' + $(this).val() + '/'; } } }) // Live event binding for close button AKA stop process $(document).on('click.precaching', 'label.closeprecaching', function(jqEvent){ $('#precaching_process').modal('hide'); }); // Live event binding for clear cache by ajax task $(document).on('click.precaching', 'button[data-role=clearcache]', function(jqEvent) { // Grab targeted sitemap link var tempTargetLink = $(this).parent().children('#jmap_seo input[data-role=sitemap_links]').val() || $(this).parent().children('#jmap_seo input[data-role=sitemap_links_sef]').attr('data-valuenosef'); // Reset always sitemap params to merge dynamically targetParsedSitemapLink = { format:null, lang:null, dataset:null, Itemid:null } targetParsedSitemapLink = $.extend(targetParsedSitemapLink, parseURL(tempTargetLink).params); deletePrecachedFile(targetParsedSitemapLink, this); }); } }; /** * Callbacks management queue * It manages a queue structure for callbacks * in FIFO fashion * * @access private * @return Object */ var callbacksQueue = function(fn) { // A new function is demanded to be added to queue if (typeof (fn) === 'function') { callbacksContainer.push(fn); } else { // No add mode, so get and return function in FIFO queue if(callbacksContainer.length) { var extractedCallback = callbacksContainer.splice(0, 1); return extractedCallback[0](); } else { // Return an empty anonymous function return function(){}; } } // Return function object just added to queue return fn; }; /** * Show progress dialog bar with informations about the ongoing started process * * @access private * @return Void */ var showProgress = function(isNew, percentage, type, status, classColor) { // No progress process injected if(isNew) { // Show second progress var progressBar = '<div class="progress progress-' + type + ' active">' + '<div id="progress_bar" class="progress-bar" role="progressbar" aria-valuenow="' + percentage + '" aria-valuemin="0" aria-valuemax="100">' + '<span class="sr-only"></span>' + '</div>' + '</div>'; // Build modal dialog var modalDialog = '<div class="modal fade" id="precaching_process" tabindex="-1" role="dialog" aria-labelledby="progressModal" aria-hidden="true">' + '<div class="modal-dialog">' + '<div class="modal-content">' + '<div class="modal-header">' + '<h4 class="modal-title">' + COM_JMAP_PRECACHING_TITLE + '</h4>' + '<label class="closeprecaching glyphicon glyphicon-remove-circle"></label>' + '<p class="modal-subtitle">' + COM_JMAP_PRECACHING_PROCESS_RUNNING + '</p>' + '</div>' + '<div class="modal-body">' + '<p>' + progressBar + '</p>' + '<p id="progress_info">' + status + '</p>' + '</div>' + '<div class="modal-footer">' + '</div>' + '</div><!-- /.modal-content -->' + '</div><!-- /.modal-dialog -->' + '</div>'; // Inject elements into content body $('body').append(modalDialog); // Setup modal var modalOptions = { backdrop:'static' }; $('#precaching_process').modal(modalOptions); // Async event progress showed and styling $('#precaching_process').on('shown.bs.modal', function(event) { $('#precaching_process div.modal-body').css({'width':'90%', 'margin':'auto'}); $('#progress_bar').css({'width':percentage + '%'}); $(startButtons).off('.precaching').addClass('disabled'); // Add an async event in the next cycle setTimeout(function(){ // Start fetching data sources server side callbacksQueue(); }, 500); }); // Remove backdrop after removing DOM modal $('#precaching_process').on('hidden.bs.modal',function(jqEvent){ $('.modal-backdrop').remove(); $(this).remove(); // Reset callbacks container callbacksContainer = new Array(); callbacksQueue(getDataSources); callbacksQueue(runProcessing); // Stop recursive promise callback onCycle = false; // Rebind events to button setTimeout(function(){ addListeners(false); $(startButtons).removeClass('disabled'); }, 3500) }); } else { // Refresh only status, progress and text $('#progress_bar').addClass(classColor) .css({'width':percentage + '%'}); $('#progress_bar').parent().removeClass('progress-normal progress-striped') .addClass('progress-' + type); $('#progress_info').html(status); // An error has been detected, so auto close process and progress bar if(classColor == 'progress-bar-danger') { setTimeout(function(){ $('#precaching_process').modal('hide'); }, 3500); } } } /** * Main recursive callback based on promises * This function is called everytime a promise is successfully resolved, * until the retrieved data sources are ended without errors and no more * data to process are still available * * @access private * @return Void */ var runProcessing = function() { // Commit ajax request, if rows processed > 0 go on with this data source, otherwise increment data source if any, otherwise process has completed var postedParams = { iteration_counter : iterationCounter, datasource_id : currentProcessedDataSource.id, process_status : processStatus, format : targetParsedSitemapLink.format, lang: targetParsedSitemapLink.lang, dataset: targetParsedSitemapLink.dataset, Itemid: targetParsedSitemapLink.Itemid }; // Request JSON2JSON var iterationPromise = $.Deferred(function(defer) { $.ajax({ type : "POST", url : "../index.php" + selectedLanguage + "?option=com_jmap&task=sitemap.doPreCaching", dataType : 'json', context : this, data : postedParams }).done(function(data, textStatus, jqXHR) { if(!data.result) { // Error found defer.reject(data.exception_message + ' - Context:' + data.context, true); return false; } // Data source has no affected rows, so finished, check if other data sources are available and go on if(!parseInt(data.affected_rows)) { defer.reject('<p>' + COM_JMAP_PRECACHING_DATA_SOURCE_COMPLETED + currentProcessedDataSource.name + '</p>', false, data); return false; } // If user has stopped processing alt execution if(!onCycle) { defer.reject('<p>' + COM_JMAP_PRECACHING_INTERRUPT + '</p>', true); return false; } // Check response all went well if(data.result && !!parseInt(data.affected_rows)) { defer.resolve(data.affected_rows); } }).fail(function(jqXHR, textStatus, errorThrown) { // Error found var genericStatus = textStatus[0].toUpperCase() + textStatus.slice(1); defer.reject('-' + genericStatus + '- ' + errorThrown, true); }); }).promise(); iterationPromise.then(function(responseData) { // Do stuff iterationCounter++; // Update process status currentProcessedLinks += parseInt(responseData); var statusMessage = '<p><span class="label label-primary">' + COM_JMAP_PRECACHING_REPORT_DATASOURCE + '<span class="badge">' + currentProcessedDataSource.name + '</span></span></p>' + '<p><span class="label label-primary">' + COM_JMAP_PRECACHING_REPORT_DATASOURCE_TYPE + '<span class="badge">' + currentProcessedDataSource.type + '</span></span></p>' + '<p><span class="label label-primary">' + COM_JMAP_PRECACHING_REPORT_LINKS + '<span class="badge">' + currentProcessedLinks + '</span></span></p>'; showProgress(false, 100, 'striped', statusMessage); // Run recursive promise callback on next data source processStatus = 'run'; runProcessing(); }, function(errorText, exception, data) { // Real exception detected, so abort processing and exit if(exception) { showProgress(false, 100, 'normal', errorText, 'progress-bar-danger'); // Prepare status for next started processing processStatus = 'start'; iterationCounter = 0; currentProcessedLinks = 0; } else { showProgress(false, 100, 'striped', errorText); // Data source has terminated, check if other data sources are available to process otherwise processing finished // Run recursive promise callback on next data source if(dataSources.length) { processStatus = 'run'; iterationCounter = 0; currentProcessedLinks = 0; currentProcessedDataSource = dataSources.pop(); runProcessing(); } else { // Last ajax call status = end to close </urlset> if(processStatus == 'run' || processStatus == 'start') { showProgress(false, 100, 'striped', COM_JMAP_PRECACHING_PROCESS_FINALIZING); processStatus = 'end'; runProcessing(); } else { // Process has been completed, no more data sources available in the stack showProgress(false, 100, 'normal', COM_JMAP_PRECACHING_PROCESS_COMPLETED, 'progress-bar-success'); // Prepare status for next started processing processStatus = 'start'; iterationCounter = 0; currentProcessedLinks = 0; // Refresh label status, successfully cached $(targetGenerationButton).parent().children('span.label') .removeClass('label-danger') .addClass('label-success') .html(COM_JMAP_PRECACHING_CACHED + '<br/>' + data.lastgeneration); // Add clear delete button here after $(targetGenerationButton).parent(':not(:has(button[data-role=clearcache]))').append(clearCacheButtons); // Close progress bar setTimeout(function(){ $('#precaching_process').modal('hide'); }, 3500); } } } }); }; /** * The first operation is get informations about published data sources * and start cycle over all the records using promises and recursion * * @access private * @return Void */ var getDataSources = function() { // Object to send to server var ajaxparams = { idtask : 'loadDataSources', template : 'json', param: {} }; // Unique param 'data' var uniqueParam = JSON.stringify(ajaxparams); // Request JSON2JSON var dataSourcePromise = $.Deferred(function(defer) { $.ajax({ type : "POST", url : "../administrator/index.php?option=com_jmap&task=ajaxserver.display&format=json", dataType : 'json', context : this, data : { data : uniqueParam } }).done(function(data, textStatus, jqXHR) { if(!data.result) { // Error found defer.reject(data.exception_message, textStatus); return false; } // No data sources found if(!data.datasources.length) { defer.reject(COM_JMAP_PRECACHING_NO_DATASOURCES_FOUND, textStatus); return false; } // Check response all went well if(data.result && data.datasources.length) { defer.resolve(data.datasources); } }).fail(function(jqXHR, textStatus, errorThrown) { // Error found var genericStatus = textStatus[0].toUpperCase() + textStatus.slice(1); defer.reject('-' + genericStatus + '- ' + errorThrown); }); }).promise(); dataSourcePromise.then(function(responseData) { // Do stuff dataSources = responseData.reverse(); // Pop the first data source retrieved currentProcessedDataSource = dataSources.pop(); // Update process status, we started showProgress(false, 100, 'striped active', COM_JMAP_PRECACHING_DATASOURCES_RETRIEVED); // Start recursive promise callback onCycle = true; callbacksQueue(); }, function(errorText, error) { // Do stuff and exit showProgress(false, 100, 'normal', errorText, 'progress-bar-danger'); }); }; /** * Get informations from server side about precached sitemaps * * @access private * @return Void */ var setPrecachedStatusLabels = function() { // Grab all links and build as array to post var availableSitemapLinks = new Array(); if($('#jmap_seo input[data-role=sitemap_links]').length) { var availableSitemapLinksWrappedSet = $('#jmap_seo input[data-role=sitemap_links]').slice(1, 7); $(availableSitemapLinksWrappedSet).each(function(index, value){ availableSitemapLinks[index] = $(value).val(); }); } else { //SEF links mode detected var availableSitemapLinksWrappedSet = $('#jmap_seo input[data-role=sitemap_links_sef]').slice(1, 7); $(availableSitemapLinksWrappedSet).each(function(index, value){ availableSitemapLinks[index] = $(value).attr('data-valuenosef'); }); } // Object to send to server var ajaxparams = { idtask : 'getPrecachedSitemaps', template : 'json', param: availableSitemapLinks }; // Unique param 'data' var uniqueParam = JSON.stringify(ajaxparams); // Request JSON2JSON var statusLabelsPromise = $.Deferred(function(defer) { $.ajax({ type : "POST", url : "../administrator/index.php?option=com_jmap&task=ajaxserver.display&format=json", dataType : 'json', context : this, data : { data : uniqueParam } }).done(function(data, textStatus, jqXHR) { if(!data.result) { // Error found defer.reject(data.exception_message, textStatus); return false; } defer.resolve(data.sitemapLinksStatus); }).fail(function(jqXHR, textStatus, errorThrown) { // Error found if(errorThrown) { var genericStatus = textStatus[0].toUpperCase() + textStatus.slice(1); defer.reject('-' + genericStatus + '- ' + errorThrown); } }); }).promise(); statusLabelsPromise.then(function(responseData) { $.each(responseData, function(url, value){ if(value.cached) { // Set label to cached $('input[value="' + url + '"], input[data-valuenosef="' + url + '"]') .parent() .children('span.label') .removeClass('label-danger') .addClass('label-success') .html(COM_JMAP_PRECACHING_CACHED + '<br/>' + value.lastgeneration); // Append clear cache button if not exists $('input[value="' + url + '"], input[data-valuenosef="' + url + '"]') .parent(':not(:has(button[data-role=clearcache]))') .children('span.label') .after(clearCacheButtons); } else { // Set label to not cached $('input[value="' + url + '"], input[data-valuenosef="' + url + '"]') .parent() .children('span.label') .removeClass('label-success') .addClass('label-danger') .html(COM_JMAP_PRECACHING_NOT_CACHED); // Remove clear cache buttons $('input[value="' + url + '"], input[data-valuenosef="' + url + '"]') .parent() .children('button[data-role=clearcache]') .remove(); } }); }, function(errorText, error) { // Show an error message retrieving precaching status $('#system-message-container').html(userMessageAlerts); $('#system-message-container span.alert-message').text(errorText); setTimeout(function(){ $('#system-message-container').slideUp(function(jqEvent){ $(this).empty().show(); }); }, 3000); }); }; /** * Delete precached sitemap files on server on demand * * @access private * @return Void */ var deletePrecachedFile = function(linksInformations, btn) { // Change button status during processing $(btn).button('loading'); // Object to send to server var ajaxparams = { idtask : 'deletePrecachedSitemap', template : 'json', param: linksInformations }; // Unique param 'data' var uniqueParam = JSON.stringify(ajaxparams); // Request JSON2JSON var deleteCachedSitemapPromise = $.Deferred(function(defer) { $.ajax({ type : "POST", url : "../administrator/index.php?option=com_jmap&task=ajaxserver.display&format=json", dataType : 'json', context : this, data : { data : uniqueParam } }).done(function(data, textStatus, jqXHR) { if(!data.result) { // Error found defer.reject(data.exception_message, textStatus); return false; } defer.resolve(); }).fail(function(jqXHR, textStatus, errorThrown) { // Error found var genericStatus = textStatus[0].toUpperCase() + textStatus.slice(1); defer.reject('-' + genericStatus + '- ' + errorThrown); }).always(function(){ // Change button status during processing $(btn).button('reset'); }); }).promise(); deleteCachedSitemapPromise.then(function(responseData) { // Ensure label has no precached state $(btn).prev().removeClass('label-success').addClass('label-danger').html(COM_JMAP_PRECACHING_NOT_CACHED); $(btn).remove(); }, function(errorText, error) { // Show an error message deleting precaching file $('#system-message-container').html(userMessageAlerts); $('#system-message-container span.alert-message').text(errorText); setTimeout(function(){ $('#system-message-container').slideUp(function(jqEvent){ $(this).empty().show(); }); }, 3000); }); }; /** * Function dummy constructor * * @access private * @param String * contextSelector * @method <<IIFE>> * @return Void */ (function __construct() { // Initialize container callbacksContainer = new Array(); callbacksQueue(getDataSources); callbacksQueue(runProcessing); // Reset counters iterationCounter = 0; currentProcessedLinks = 0; // Initialize process status processStatus = 'start'; onCycle = false; // Add UI events addListeners.call(this, true); // Initialize as empty to avoid JS errors targetParsedSitemapLink = { format:null, lang:null, dataset:null, Itemid:null } // No multilanguage cases selectedLanguage = ''; // Set current language if any if($('#language_option').length) { selectedLanguage = '/' + $('#language_option').val() + '/'; } // Start grabbing informations about precached sitemaps links setPrecachedStatusLabels(); }).call(this); } // On DOM Ready $(function() { window.JMapXmlPrecaching = new XmlPrecaching(); }); })(jQuery);