/** * system route module */ define([ 'jquery', 'app/init', 'app/util', 'bootbox' ], function($, Init, Util, bootbox) { 'use strict'; var config = { // module info moduleClass: 'pf-module', // class for each module routeCacheTTL: 10, // route cache timer (client) in seconds // system route module systemRouteModuleClass: 'pf-system-route-module', // class for this module // headline toolbar systemModuleHeadlineIcon: 'pf-module-icon-button', // class for toolbar icons in the head systemModuleHeadlineIconSearch: 'pf-module-icon-button-search', // class for "search" icon systemModuleHeadlineIconRefresh: 'pf-module-icon-button-refresh', // class for "refresh" icon systemSecurityClassPrefix: 'pf-system-security-', // prefix class for system security level (color) // dialog routeDialogId: 'pf-route-dialog', // id for route dialog systemDialogSelectClass: 'pf-system-dialog-select', // class for system select Element systemInfoRoutesTableClass: 'pf-system-route-table', // class for route tables mapSelectId: 'pf-route-dialog-map-select', // id for "map" select dataTableActionCellClass: 'pf-table-action-cell' // class for "action" cells }; // cache for system routes var cache = { systemRoutes: {} // jump information between solar systems }; /** * callback function, adds new row to a dataTable with jump information for a route * @param context * @param routesData */ var callbackAddRouteRow = function(context, routesData){ if(routesData.length > 0){ for(var i = 0; i < routesData.length; i++){ var routeData = routesData[i]; // format routeData var rowData = formatRouteData(routeData); if(rowData.route){ var cacheKey = routeData.systemFromData.name.toLowerCase() + '_' + routeData.systemToData.name.toLowerCase(); // update route cache cache.systemRoutes[cacheKey] = { data: rowData, updated: Util.getServerTime().getTime() / 1000 }; var rowElement = addRow(context, rowData); rowElement.initTooltips({ container: 'body' }); } } // redraw dataTable context.dataTable.draw(); } }; /** * add a new dataTable row to the routes table * @param context * @param rowData * @returns {*} */ var addRow = function(context, rowData){ var dataTable = context.dataTable; var rowElement = null; var row = null; var animationStatus = 'changed'; // search for an existing row (e.g. on mass "table refresh" [all routes]) // get rowIndex where column 0 (equals to "systemToData.name") matches rowData.systemToData.name var indexes = dataTable.rows().eq(0).filter( function (rowIdx) { return (dataTable.cell(rowIdx, 0 ).data().name === rowData.systemToData.name); }); if(indexes.length > 0){ // update row with FIRST index // -> systemFrom should be unique! row = dataTable.row( parseInt(indexes[0]) ); // update row data row.data(rowData); }else{ // no existing route found -> add new row row = dataTable.row.add( rowData ); animationStatus = 'added'; } if(row.length > 0){ rowElement = row.nodes().to$(); if(animationStatus !== null){ rowElement.data('animationStatus', animationStatus); } } return rowElement; }; /** * update complete routes table (refresh all) * @param moduleElement * @param dataTable */ var updateRoutesTable = function(moduleElement, dataTable){ var context = { moduleElement: moduleElement, dataTable: dataTable }; var routeData = []; dataTable.rows().every( function() { routeData.push( getRouteRequestDataFromRowData( this.data() )); }); getRouteData({routeData: routeData}, context, callbackAddRouteRow); }; /** * format rowData for route search/update request * @param {Object} rowData * @returns {Object} */ var getRouteRequestDataFromRowData = function(rowData){ return { mapIds: (rowData.hasOwnProperty('mapIds')) ? rowData.mapIds : [], systemFromData: (rowData.hasOwnProperty('systemFromData')) ? rowData.systemFromData : {}, systemToData: (rowData.hasOwnProperty('systemToData')) ? rowData.systemToData : {}, stargates: (rowData.hasOwnProperty('stargates')) ? rowData.stargates : 1, jumpbridges: (rowData.hasOwnProperty('jumpbridges')) ? rowData.jumpbridges : 1, wormholes: (rowData.hasOwnProperty('wormholes')) ? rowData.wormholes : 1, wormholesReduced: (rowData.hasOwnProperty('wormholesReduced')) ? rowData.wormholesReduced : 1, wormholesCritical: (rowData.hasOwnProperty('wormholesCritical')) ? rowData.wormholesCritical : 1 }; }; /** * show route dialog. User can search for systems and jump-info for each system is added to a data table * @param dialogData */ var showFindRouteDialog = function(dialogData){ var mapSelectOptions = []; var currentMapData = Util.getCurrentMapData(); if(currentMapData !== false){ for(var i = 0; i < currentMapData.length; i++){ mapSelectOptions.push({ id: currentMapData[i].config.id, name: currentMapData[i].config.name, selected: (dialogData.mapId === currentMapData[i].config.id) }); } } var data = { id: config.routeDialogId, selectClass: config.systemDialogSelectClass, mapSelectId: config.mapSelectId, systemFromData: dialogData.systemFromData, mapSelectOptions: mapSelectOptions }; requirejs(['text!templates/dialog/route.html', 'mustache'], function(template, Mustache) { var content = Mustache.render(template, data); var findRouteDialog = bootbox.dialog({ title: 'Route finder', message: content, show: false, buttons: { close: { label: 'cancel', className: 'btn-default', callback: function(){ $(findRouteDialog).modal('hide'); } }, success: { label: ' search route', className: 'btn-primary', callback: function () { // add new route to route table // get form Values var form = $('#' + config.routeDialogId).find('form'); var routeDialogData = $(form).getFormValues(); // validate form form.validator('validate'); // check whether the form is valid var formValid = form.isValidForm(); if(formValid === false){ // don't close dialog return false; } // get all system data from select2 var systemSelectData = form.find('.' + config.systemDialogSelectClass).select2('data'); if( systemSelectData && systemSelectData.length === 1 ){ var context = { moduleElement: dialogData.moduleElement, dataTable: dialogData.dataTable }; var requestData = { routeData: [{ mapIds: routeDialogData.mapIds, systemFromData: dialogData.systemFromData, systemToData: { systemId: systemSelectData[0].systemId, name: systemSelectData[0].text }, stargates: routeDialogData.hasOwnProperty('stargates') ? parseInt( routeDialogData.stargates ) : 0, jumpbridges: routeDialogData.hasOwnProperty('jumpbridges') ? parseInt( routeDialogData.jumpbridges ) : 0, wormholes: routeDialogData.hasOwnProperty('wormholes') ? parseInt( routeDialogData.wormholes ) : 0, wormholesReduced: routeDialogData.hasOwnProperty('wormholesReduced') ? parseInt( routeDialogData.wormholesReduced ) : 0, wormholesCritical: routeDialogData.hasOwnProperty('wormholesCritical') ? parseInt( routeDialogData.wormholesCritical ) : 0 }] }; getRouteData(requestData, context, callbackAddRouteRow); } } } } }); findRouteDialog.on('show.bs.modal', function(e) { findRouteDialog.initTooltips(); // init some dialog/form observer setDialogObserver( $(this) ); // init map select ---------------------------------------------------------------- var mapSelect = $(this).find('#' + config.mapSelectId); mapSelect.initMapSelect(); }); findRouteDialog.on('shown.bs.modal', function(e) { // init system select live search ------------------------------------------------ // -> add some delay until modal transition has finished var systemTargetSelect = $(this).find('.' + config.systemDialogSelectClass); systemTargetSelect.delay(240).initSystemSelect({key: 'name'}); }); // show dialog findRouteDialog.modal('show'); }); }; /** * set event observer for route finder dialog * @param routeDialog */ var setDialogObserver = function(routeDialog){ var wormholeCheckbox = routeDialog.find('input[type="checkbox"][name="wormholes"]'); var wormholeReducedCheckbox = routeDialog.find('input[type="checkbox"][name="wormholesReduced"]'); var wormholeCriticalCheckbox = routeDialog.find('input[type="checkbox"][name="wormholesCritical"]'); // store current "checked" state for each box --------------------------------------------- var storeCheckboxStatus = function(){ wormholeReducedCheckbox.data('selectState', wormholeReducedCheckbox.prop('checked')); wormholeCriticalCheckbox.data('selectState', wormholeCriticalCheckbox.prop('checked')); }; // on wormhole checkbox change ------------------------------------------------------------ var onWormholeCheckboxChange = function(){ if( $(this).is(':checked') ){ wormholeReducedCheckbox.prop('disabled', false); wormholeCriticalCheckbox.prop('disabled', false); wormholeReducedCheckbox.prop('checked', wormholeReducedCheckbox.data('selectState')); wormholeCriticalCheckbox.prop('checked', wormholeCriticalCheckbox.data('selectState')); }else{ storeCheckboxStatus(); wormholeReducedCheckbox.prop('checked', false); wormholeReducedCheckbox.prop('disabled', true); wormholeCriticalCheckbox.prop('checked', false); wormholeCriticalCheckbox.prop('disabled', true); } }.bind(wormholeCheckbox); wormholeCheckbox.on('change', onWormholeCheckboxChange); // initial checkbox check storeCheckboxStatus(); onWormholeCheckboxChange(); }; /** * requests route data from eveCentral API and execute callback * @param requestData * @param context * @param callback */ var getRouteData = function(requestData, context, callback){ context.moduleElement.showLoadingAnimation(); $.ajax({ url: Init.path.searchRoute, type: 'POST', dataType: 'json', data: requestData, context: context }).done(function(routesData){ this.moduleElement.hideLoadingAnimation(); // execute callback callback(this, routesData.routesData); }); }; /** * format route data from API request into dataTable row format * @param routeData * @returns {{}} */ var formatRouteData = function(routeData){ var reloadButton = ''; var deleteButton = ''; // default row data (e.g. no route found) var tableRowData = { systemFromData: routeData.systemFromData, systemToData: routeData.systemToData, jumps: { value: 0, formatted: '---' }, avgTrueSec: { value: '', formatted: '' }, route: 'not found', stargates: routeData.stargates, jumpbridges: routeData.jumpbridges, wormholes: routeData.wormholes, wormholesReduced: routeData.wormholesReduced, wormholesCritical: routeData.wormholesCritical, reload: { button: reloadButton }, clear: { button: deleteButton }, maps: routeData.maps, mapIds: routeData.mapIds //map data (mapIds is "redundant") }; if( routeData.routePossible === true && routeData.route.length > 0 ){ // route data available // add route Data var jumpData = []; var avgSecTemp = 0; // loop all systems on this route for(var i = 0; i < routeData.route.length; i++){ var routeNodeData = routeData.route[i]; // format system name (camelCase) var systemName = routeNodeData.system.charAt(0).toUpperCase() + routeNodeData.system.slice(1).toLowerCase(); var systemSec = Number(routeNodeData.security).toFixed(1).toString(); var tempSystemSec = systemSec; if(tempSystemSec <= 0){ tempSystemSec = '0-0'; } var systemSecClass = config.systemSecurityClassPrefix + tempSystemSec.replace('.', '-'); var system = ''; jumpData.push( system ); // "source" system is not relevant for average security if(i > 0){ avgSecTemp += Number(routeNodeData.security); } } var avgSec = ( avgSecTemp / (routeData.route.length - 1)).toFixed(2); var avgSecForClass = Number(avgSec).toFixed(1); if(avgSecForClass <= 0){ avgSecForClass = '0.0'; } var avgSecClass = config.systemSecurityClassPrefix + avgSecForClass.toString().replace('.', '-'); tableRowData.jumps = { value: routeData.routeJumps, formatted: routeData.routeJumps }; tableRowData.avgTrueSec = { value: avgSec, formatted: '' + avgSec + '' }; tableRowData.route = jumpData.join(' '); } return tableRowData; }; /** * get the route finder moduleElement * @returns {*} */ var getModule = function(){ // create new module container var moduleElement = $('
', { class: [config.moduleClass, config.systemRouteModuleClass].join(' ') }); // headline toolbar icons var headlineToolbar = $('
', { class: 'pull-right' }).append( $('', { class: ['fa', 'fa-fw', 'fa-search', config.systemModuleHeadlineIcon, config.systemModuleHeadlineIconSearch].join(' '), title: 'find route' }).attr('data-html', 'true').attr('data-toggle', 'tooltip'), $('', { class: ['fa', 'fa-fw', 'fa-refresh', config.systemModuleHeadlineIcon, config.systemModuleHeadlineIconRefresh].join(' '), title: 'refresh all' }).attr('data-html', 'true').attr('data-toggle', 'tooltip') ); moduleElement.append(headlineToolbar); // headline var headline = $('
', { class: 'pull-left', text: 'Routes' }); moduleElement.append(headline); // crate new route table var table = $('', { class: ['compact', 'stripe', 'order-column', 'row-border', config.systemInfoRoutesTableClass].join(' ') }); moduleElement.append( $(table) ); // init empty table var routesTable = table.DataTable( { paging: false, ordering: true, order: [ 1, 'asc' ], info: false, searching: false, hover: false, autoWidth: false, rowId: 'systemTo', language: { emptyTable: 'No routes added' }, columnDefs: [ { targets: 0, orderable: true, title: 'system   ', class: Util.config.popoverTriggerClass, data: 'systemToData', render: { _: 'name', sort: 'name' }, createdCell: function(cell, cellData, rowData, rowIndex, colIndex) { // init context menu $(cell).initSystemPopover({ systemToData: rowData.systemToData }); } },{ targets: 1, orderable: true, title: '  ', width: '18px', class: 'text-right', data: 'jumps', render: { _: 'formatted', sort: 'value' } },{ targets: 2, orderable: true, title: 'Ø  ', width: '15px', class: 'text-right', data: 'avgTrueSec', render: { _: 'formatted', sort: 'value' } },{ targets: 3, orderable: false, title: 'route', data: 'route' },{ targets: 4, title: '', orderable: false, searchable: false, width: '10px', class: ['text-center', config.dataTableActionCellClass].join(' '), data: 'reload', render: { _: 'button' }, createdCell: function(cell, cellData, rowData, rowIndex, colIndex){ var tempTableApi = this.api(); $(cell).on('click', function(e) { // get current row data (important!) // -> "rowData" param is not current state, values are "on createCell()" state rowData = tempTableApi.row( $(cell).parents('tr')).data(); var context = { moduleElement: moduleElement, dataTable: tempTableApi }; var requestData = { routeData: [{ mapIds: rowData.mapIds, systemFromData: rowData.systemFromData, systemToData: rowData.systemToData, stargates: rowData.stargates ? 1 : 0, jumpbridges: rowData.jumpbridges ? 1 : 0, wormholes: rowData.wormholes ? 1 : 0, wormholesReduced: rowData.wormholesReduced ? 1 : 0, wormholesCritical: rowData.wormholesCritical ? 1 : 0 }] }; getRouteData(requestData, context, callbackAddRouteRow); }); } },{ targets: 5, title: '', orderable: false, searchable: false, width: '10px', class: ['text-center', config.dataTableActionCellClass].join(' '), data: 'clear', render: { _: 'button' }, createdCell: function(cell, cellData, rowData, rowIndex, colIndex){ var tempTableElement = this; var confirmationSettings = { container: 'body', placement: 'left', btnCancelClass: 'btn btn-sm btn-default', btnCancelLabel: 'cancel', btnCancelIcon: 'fa fa-fw fa-ban', title: 'delete route', btnOkClass: 'btn btn-sm btn-danger', btnOkLabel: 'delete', btnOkIcon: 'fa fa-fw fa-close', onConfirm : function(e, target){ var deleteRowElement = $(cell).parents('tr'); tempTableElement.api().rows(deleteRowElement).remove().draw(); } }; // init confirmation dialog $(cell).confirmation(confirmationSettings); } } ], drawCallback: function(settings){ var animationRows = this.api().rows().nodes().to$().filter(function() { return ( $(this).data('animationStatus') || $(this).data('animationTimer') ); }); for(var i = 0; i < animationRows.length; i++){ $(animationRows[i]).pulseTableRow($(animationRows[i]).data('animationStatus')); $(animationRows[i]).removeData('animationStatus'); } }, data: [] // will be added dynamic }); // init tooltips for this module var tooltipElements = moduleElement.find('[data-toggle="tooltip"]'); tooltipElements.tooltip({ container: 'body' }); return moduleElement; }; /** * init system popover (e.g. for setWaypoints) * @param options */ $.fn.initSystemPopover = function(options){ var elements = $(this); var eventNamespace = 'hideSystemPopup'; var systemToData = options.systemToData; requirejs(['text!templates/tooltip/system_popover.html', 'mustache'], function (template, Mustache) { var data = { systemToData: systemToData }; var content = Mustache.render(template, data); elements.each(function() { var element = $(this); // destroy "popover" and remove "click" event for animation element.popover('destroy').off(); // init popover and add specific class to it (for styling) element.popover({ html: true, title: systemToData.name, trigger: 'manual', placement: 'top', container: 'body', content: content }).data('bs.popover').tip().addClass('pf-popover'); }); // set popup "close" observer elements.initPopoverClose(eventNamespace); // set "open" trigger on "right click" // -> this is not supported by the "trigger" param in .popover(); // -> therefore we need to set it up manually elements.on('contextmenu', function(e){ e.preventDefault(); $(this).popover('show'); }); // set link observer "on shown" event elements.on('shown.bs.popover', function () { var popoverRoot = $(this); popoverRoot.data('bs.popover').tip().find('a').on('click', function(){ // hint: "data" attributes should be in lower case! var systemData = { name: $(this).data('name'), systemId: $(this).data('systemid') }; Util.setDestination(systemData, 'set_destination'); // close popover popoverRoot.popover('hide'); }); }); }); }; /** * init route module * -> request route path fore "default" trade hub systems * @param moduleElement * @param mapId * @param systemData */ var initModule = function(moduleElement, mapId, systemData){ var systemFromData = { name: systemData.name, systemId: systemData.systemId }; var systemsTo = [ { name: 'Jita', systemId: 30000142 },{ name: 'Amarr', systemId: 30002187 },{ name: 'Rens', systemId: 30002510 },{ name: 'Dodixie', systemId: 30002659 } ]; var routesTableElement = moduleElement.find('.' + config.systemInfoRoutesTableClass); var routesTable = routesTableElement.DataTable(); // init refresh routes -------------------------------------------------------------------- moduleElement.find('.' + config.systemModuleHeadlineIconRefresh).on('click', function(e){ updateRoutesTable(moduleElement, routesTable); }); // init search routes dialog -------------------------------------------------------------- moduleElement.find('.' + config.systemModuleHeadlineIconSearch).on('click', function(e){ var dialogData = { moduleElement: moduleElement, mapId: mapId, systemFromData: systemFromData, dataTable: routesTable }; showFindRouteDialog(dialogData); }); // fill routesTable with data ------------------------------------------------------------- var requestRouteData = []; var currentTimestamp = Util.getServerTime().getTime(); for(var i = 0; i < systemsTo.length; i++){ var systemToData = systemsTo[i]; if(systemFromData.name !== systemToData.name){ var cacheKey = 'route_' + mapId + '_' + systemFromData.name.toUpperCase() + '_' + systemToData.name.toUpperCase(); if( cache.systemRoutes.hasOwnProperty(cacheKey) && Math.round( ( currentTimestamp - (new Date( cache.systemRoutes[cacheKey].updated * 1000).getTime())) / 1000 ) <= config.routeCacheTTL ){ // route data is cached (client side) var context = { dataTable: routesTable }; addRow(context, cache.systemRoutes[cacheKey].data); }else{ // get route data var searchData = { mapIds: [mapId], systemFromData: systemFromData, systemToData: systemToData }; requestRouteData.push( getRouteRequestDataFromRowData( searchData )); } } } // check if routes data is not cached and is requested if(requestRouteData.length > 0){ var contextData = { moduleElement: moduleElement, dataTable: routesTable }; var requestData = { routeData: requestRouteData }; getRouteData(requestData, contextData, callbackAddRouteRow); } }; /** * updates an dom element with the system route module * @param mapId * @param systemData */ $.fn.drawSystemRouteModule = function(mapId, systemData){ var parentElement = $(this); // show route module var showModule = function(moduleElement){ if(moduleElement){ moduleElement.css({ opacity: 0 }); parentElement.append(moduleElement); moduleElement.velocity('transition.slideDownIn', { duration: Init.animationSpeed.mapModule, delay: Init.animationSpeed.mapModule, complete: function(){ initModule(moduleElement, mapId, systemData); } }); } }; // check if module already exists var moduleElement = parentElement.find('.' + config.systemRouteModuleClass); if(moduleElement.length > 0){ moduleElement.velocity('transition.slideDownOut', { duration: Init.animationSpeed.mapModule, complete: function(tempElement){ $(tempElement).remove(); moduleElement = getModule(); showModule(moduleElement); } }); }else{ moduleElement = getModule(); showModule(moduleElement); } }; });