Files
pathfinder/js/app/ui/system_signature.js
Exodus4D 0a14b8fdae - added requirement check for "PDO", "PDO_MYSQL" to "/setup" route
- imrpved DB PDO connection (added "TIMEOUT", forced "ERRMODE")
- fixed broken "system alias" renaming dialog
2016-07-26 19:16:04 +02:00

1994 lines
72 KiB
JavaScript

/**
* System signature module
*/
define([
'jquery',
'app/init',
'app/util',
'app/render',
'bootbox'
], function($, Init, Util, Render, bootbox) {
'use strict';
var config = {
// module info
moduleClass: 'pf-module', // class for each module
// system signature module
systemSigModuleClass: 'pf-sig-table-module', // module wrapper
// tables
tableToolsClass: 'pf-table-tools', // class for table toolbar
tableToolsActionClass: 'pf-table-tools-action', // class for table toolbar action
// dialogs
signatureReaderDialogId: 'pf-signature-reader-dialog', // id for signature reader dialog
// signature progress bar
signatureScannedProgressBarClass: 'pf-system-progress-scanned', // class for signature progress bar
// toolbar
sigTableClearButtonClass: 'pf-sig-table-clear-button', // class for "clear" signatures button
// signature table
sigTableClass: 'pf-sig-table', // Table class for all Signature Tables
sigTablePrimaryClass: 'pf-sig-table-primary', // class for primary sig table
sigTableSecondaryClass: 'pf-sig-table-secondary', // class for secondary sig table
sigTableRowIdPrefix: 'pf-sig-table-row-', // id prefix for a table row <tr>
sigTableEditText: 'pf-sig-table-edit-text', // class for editable fields (text)
sigTableEditSigNameInput: 'pf-sig-table-edit-name-input', // class for editable fields (input)
sigTableEditSigGroupSelect: 'pf-sig-table-edit-group-select', // class for editable fields (sig group)
sigTableEditSigTypeSelect: 'pf-sig-table-edit-type-select', // class for editable fields (sig type)
sigTableEditSigDescriptionTextarea: 'pf-sig-table-edit-desc-text', // class for editable fields (sig description)
sigTableCreatedCellClass: 'pf-sig-table-created', // class for "created" cells
sigTableUpdatedCellClass: 'pf-sig-table-updated', // class for "updated" cells
sigTableCounterClass: 'pf-table-counter-cell', // class for "counter" cells
sigTableActionCellClass: 'pf-table-action-cell', // class for "action" cells
// xEditable
editableDiscriptionInputClass: 'pf-editable-description' // class for "description" textarea
};
// lock Signature Table update temporary (until. some requests/animations) are finished
var disableTableUpdate = true;
// disable "copy&paste" from clipboard (until request finished)
var disableCopyFromClipboard = false;
// cache for dataTable object
var signatureTable = null;
// empty signatureData object -> for "add row" table
var emptySignatureData = {
id: 0,
name: '',
groupId: 0,
typeId: 0,
description: '',
created: {
created: null
},
updated: {
updated: null
}
};
var fullSignatureOptions = {
action: 'delete',
actionClass: ['fa-close', 'txt-color', 'txt-color-redDarker'].join(' ')
};
// empty signatureData row Options
var emptySignatureOptions = {
action: 'add',
actionClass: ['fa-plus'].join(' ')
};
var sigNameCache = {}; // cache signature names
var validSignatureNames = [ // allowed signature type/names
'Cosmic Anomaly',
'Cosmic Signature',
'Космическая аномалия', // == "Cosmic Anomaly"
'Источники сигналов' // == "Cosmic Signature"
];
/**
* collect all data of all editable fields in a signature table
* @returns {Array}
*/
var getSignatureTableData = function(){
var signatureTableApi = signatureTable.api();
var tableData = [];
signatureTableApi.rows().eq(0).each(function(idx){
var row = signatureTableApi.row(idx);
// default row data
var defaultRowData = row.data();
var rowElement = row.nodes().to$();
if(defaultRowData.id > 0){
// get all editable fields per row
var editableFields = rowElement.find('.editable');
if(editableFields.length > 0){
var values = $(editableFields).editable('getValue');
// convert to lower for better compare options
values.name = values.name.toLowerCase();
// add pk for this row
values.id = defaultRowData.id;
// add updated for this row
values.updated = defaultRowData.updated;
// add row index
values.index = idx;
tableData.push( values );
}
}
});
return tableData;
};
/**
* updates a single cell with new data (e.g. "updated" cell)
* @param rowElement
* @param cellIndex
* @param data
*/
var updateSignatureCell = function(rowElement, cellIndex, data){
var signatureTableApi = signatureTable.api();
var rowIndex = signatureTableApi.row( rowElement ).index();
var updateCell = signatureTableApi.cell( rowIndex, cellIndex );
var updateCellElement = updateCell.nodes().to$();
if(cellIndex === 6){
// clear existing counter interval
clearInterval( updateCellElement.data('interval') );
}
// set new value
updateCell.data( data ).draw();
if(cellIndex === 6){
updateCellElement.initTimestampCounter();
}
};
/**
* Updates a signature table, changes all signatures where name matches
* add all new signatures as a row
*
* @param signatureDataOrig
* @param deleteOutdatedSignatures -> set to "true" if signatures should be deleted that are not included in "signatureData"
*/
$.fn.updateSignatureTable = function(signatureDataOrig, deleteOutdatedSignatures){
// check if table update is allowed
if(disableTableUpdate === true){
return;
}
// clone signature array because of further manipulation
var signatureData = $.extend([], signatureDataOrig);
// disable update until function is ready;
lockSignatureTable();
var moduleElement = $(this);
// get signature table API
var signatureTableApi = signatureTable.api();
// get current system data
var currentSystemData = Util.getCurrentSystemData();
var tableData = getSignatureTableData();
var notificationCounter = {
added: 0,
changed: 0,
deleted: 0
};
for(var i = 0; i < signatureData.length; i++){
for(var j = 0; j < tableData.length; j++){
if(signatureData[i].id === tableData[j].id){
// check if row was updated
if(signatureData[i].updated.updated > tableData[j].updated.updated){
// row element to remove
var currentRowElement = signatureTableApi.row(tableData[j].index).nodes().to$();
// hide open editable fields on the row before removing them
currentRowElement.find('.editable').editable('destroy');
// remove "old" row
signatureTableApi.row(currentRowElement).remove().draw();
// and add "new" row
var changedRowElement = addSignatureRow(currentSystemData.systemData, signatureData[i], false);
// highlight
changedRowElement.pulseTableRow('changed');
notificationCounter.changed++;
}
// remove signature data -> all left signatures will be added
signatureData.splice(i, 1);
i--;
// remove signature data -> all left signatures will be deleted
tableData.splice(j, 1);
j--;
break;
}
}
}
// delete signatures ------------------------------------------------------------------------------------------
if(deleteOutdatedSignatures === true){
// callback function after row deleted
var toggleTableRowCallback = function(tempRowElement){
// hide open editable fields on the row before removing them
tempRowElement.find('.editable').editable('destroy');
// delete signature row
signatureTableApi.row(tempRowElement).remove().draw();
};
for(var l = 0; l < tableData.length; l++){
var rowElement = signatureTableApi.row(tableData[l].index).nodes().to$();
rowElement.toggleTableRow(toggleTableRowCallback);
notificationCounter.deleted++;
}
}
// add new signatures -----------------------------------------------------------------------------------------
for(var k = 0; k < signatureData.length; k++){
// and add "new" row
var newRowElement = addSignatureRow(currentSystemData.systemData, signatureData[k], false);
// highlight
newRowElement.pulseTableRow('added');
notificationCounter.added++;
}
// show notification ------------------------------------------------------------------------------------------
if(
notificationCounter.added > 0 ||
notificationCounter.changed > 0 ||
notificationCounter.deleted > 0
){
// update signature bar
moduleElement.updateScannedSignaturesBar({showNotice: true});
// show Notification
var notification = notificationCounter.added + ' added<br>';
notification += notificationCounter.changed + ' changed<br>';
notification += notificationCounter.deleted + ' deleted<br>';
Util.showNotify({title: 'Signatures updated', text: notification, type: 'success'});
// wait until add/remove animations are finished before enable table for auto update again
unlockSignatureTable(false);
}else{
// enable table for next update
unlockSignatureTable(true);
}
};
/**
* lock system signature table for
*/
var lockSignatureTable = function(){
disableTableUpdate = true;
};
/**
* unlock system signature table from been locked
* -> make table "update-able" again
* @param instant
*/
var unlockSignatureTable = function(instant){
if(disableTableUpdate === true){
if(instant === true){
disableTableUpdate = false;
}else{
// wait until add/remove animations are finished before enable table for auto update again
setTimeout(function(){ disableTableUpdate = false; }, 2000);
}
}
};
/**
* update Progressbar for all scanned signatures in a system
* @param options
*/
$.fn.updateScannedSignaturesBar = function(options){
var moduleElement = $(this);
// get progress bar
var progressBarWrapper = moduleElement.find('.' + config.signatureScannedProgressBarClass);
var progressBar = $(progressBarWrapper).find('.progress-bar');
var progressBarLabel = $(progressBarWrapper).find('.progress-label-right');
var tableData = getSignatureTableData();
var percent = 0;
var progressBarType = 'progress-bar-danger';
if(tableData){
var sigCount = tableData.length;
var sigIncompleteCount = 0;
// check for signatures without "groupId" -> these are un scanned signatures
for(var i = 0; i < tableData.length; i++){
var groupId = parseInt(tableData[i].groupId);
if(groupId === 0){
sigIncompleteCount++;
}
}
if(sigCount > 0){
percent = 100 - Math.round( 100 / sigCount * sigIncompleteCount );
}
if(percent < 30){
progressBarType = 'progress-bar-danger' ;
}else if(percent < 100){
progressBarType = 'progress-bar-warning';
}else{
progressBarType = 'progress-bar-success';
}
}
setTimeout(
function() {
progressBarLabel.text(percent + '%');
progressBar.removeClass().addClass('progress-bar').addClass(progressBarType);
progressBar.attr('aria-valuenow', percent);
progressBar.css({width: percent + '%'});
var notification = (sigCount - sigIncompleteCount) + ' / ' + sigCount + ' (' + percent + '%) signatures scanned';
// show notifications
if(options.showNotice !== false){
if(percent < 100){
Util.showNotify({title: 'Unscanned signatures', text: notification, type: 'info'});
}else{
Util.showNotify({title: 'System is scanned', text: notification, type: 'success'});
}
}
}, 100);
};
/**
* open "signature reader" dialog for signature table
* @param systemData
*/
$.fn.showSignatureReaderDialog = function(systemData){
var moduleElement = $(this);
var data = {
id: config.signatureReaderDialogId
};
requirejs(['text!templates/dialog/signature_reader.html', 'mustache'], function(template, Mustache) {
var content = Mustache.render(template, data);
var signatureReaderDialog = bootbox.dialog({
title: 'Signature reader',
message: content,
buttons: {
close: {
label: 'cancel',
className: 'btn-default',
callback: function(){
$(signatureReaderDialog).modal('hide');
}
},
success: {
label: '<i class="fa fa-clipboard fa-fw"></i>&nbsp;update signatures',
className: 'btn-success',
callback: function () {
// get form Values
var form = $('#' + config.signatureReaderDialogId).find('form');
var formData = $(form).getFormValues();
var signatureOptions = {
deleteOld: (formData.deleteOld) ? 1 : 0
};
moduleElement.updateSignatureTableByClipboard(systemData, formData.clipboard, signatureOptions);
}
}
}
});
// dialog shown event
signatureReaderDialog.on('shown.bs.modal', function(e) {
signatureReaderDialog.initTooltips();
// set focus on sig-input textarea
signatureReaderDialog.find('textarea').focus();
});
});
};
/**
* updates the signature table with all signatures pasted into the "signature reader" dialog
* -> Hint: copy&paste signature data (without any open dialog) will add signatures as well
* @param systemData
* @param clipboard data stream
* @param options
*/
$.fn.updateSignatureTableByClipboard = function(systemData, clipboard, options){
// check if copy&paste is enabled
if( !disableCopyFromClipboard ){
var moduleElement = $(this);
// parse input stream
var signatureData = parseSignatureString(systemData, clipboard);
if(signatureData.length > 0){
// save signature data
// lock update function until request is finished
lockSignatureTable();
// lock copy during request (prevent spamming (ctrl + c )
disableCopyFromClipboard = true;
var requestData = {
signatures: signatureData,
deleteOld: (options.deleteOld) ? 1 : 0,
systemId: parseInt(systemData.id)
};
$.ajax({
type: 'POST',
url: Init.path.saveSignatureData,
data: requestData,
dataType: 'json'
}).done(function(responseData){
unlockSignatureTable(true);
// updates table with new/updated signature information
moduleElement.updateSignatureTable(responseData.signatures, false);
}).fail(function( jqXHR, status, error) {
var reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': Update signatures', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
}).always(function() {
unlockSignatureTable(true);
disableCopyFromClipboard = false;
});
}
}
};
/**
* parses a copy&paste string from ingame scanning window and parses it
* @param systemData
* @param clipboard
* @returns {Array}
*/
var parseSignatureString = function(systemData, clipboard){
var signatureData = [];
if(clipboard.length){
var signatureRows = clipboard.split(/\r\n|\r|\n/g);
var signatureGroupOptions = Util.getSignatureGroupInfo('name');
var invalidSignatures = 0;
for(var i = 0; i < signatureRows.length; i++){
var rowData = signatureRows[i].split(/\t/g);
if(rowData.length === 6){
// check if sig Type = anomaly or combat site
if(validSignatureNames.indexOf( rowData[1] ) !== -1){
var sigGroup = $.trim(rowData[2]).toLowerCase();
var sigDescription = $.trim(rowData[3]);
var sigGroupId = 0;
var typeId = 0;
// get groupId by groupName
for (var prop in signatureGroupOptions) {
if(signatureGroupOptions.hasOwnProperty(prop)){
if(signatureGroupOptions[prop] === sigGroup){
sigGroupId = parseInt( prop );
break;
}
}
}
// wormhole type cant be extracted from signature string -> skip function call
if(sigGroupId !== 5){
// try to get "typeId" by description string
typeId = Util.getSignatureTypeIdByName( systemData, sigGroupId, sigDescription );
// set signature name as "description" if signature matching failed
sigDescription = (typeId === 0) ? sigDescription : '';
}else{
sigDescription = '';
}
// map array values to signature Object
var signatureObj = {
systemId: systemData.id,
name: $.trim( rowData[0].substr(0, 3) ).toLowerCase(),
groupId: sigGroupId,
typeId: typeId,
description: sigDescription
};
signatureData.push(signatureObj);
}else{
invalidSignatures++;
}
}else{
invalidSignatures++;
}
}
if(invalidSignatures > 0){
var notification = invalidSignatures + ' / ' + signatureRows.length + ' signatures invalid';
Util.showNotify({title: 'Invalid signature(s)', text: notification, type: 'warning'});
}
}
return signatureData;
};
/**
* get a labeled button
* @param options
* @returns {*|jQuery}
*/
var getLabledButton = function(options){
var buttonClasses = ['btn', 'btn-sm', 'btn-labeled'];
switch(options.type){
case 'default':
buttonClasses.push('btn-default');
break;
case 'primary':
buttonClasses.push('btn-primary');
break;
case 'danger':
buttonClasses.push('btn-danger');
break;
}
// add optional classes
if(options.classes){
buttonClasses = buttonClasses.concat(options.classes);
}
var buttonElement = $('<button>', {
class: buttonClasses.join(' '),
type: 'button',
html: '&nbsp;' + options.label + '&nbsp;&nbsp;'
}).on('click', function(e){
options.onClick(e);
}).prepend(
$('<span>', {
class: 'btn-label'
}).prepend(
$('<i>', {
class: ['fa', options.icon, 'fa-fw'].join(' ')
})
)
);
// add optional badge
if(options.badge){
buttonElement.append(
$('<span>', {
class: ['badge'].join(' '),
text: options.badge.label
})
);
}
return buttonElement;
};
/**
* draw signature table toolbar (add signature button, scan progress bar
* @param systemData
*/
$.fn.drawSignatureTableToolbar = function(systemData){
var moduleElement = $(this);
// add toolbar buttons for table ------------------------------------------------------------------------------
var tableToolbar = $('<div>', {
class: config.tableToolsClass
}).append(
getLabledButton({
type: 'primary',
label: 'add',
icon: 'fa-plus',
onClick: function(e){
// show "add sig" div
var toolsElement = $(e.target).parents('.' + config.moduleClass).find('.' + config.tableToolsActionClass);
// set toggle animation
if(toolsElement.is(':visible')){
toolsElement.velocity('stop').velocity('reverse');
}else{
toolsElement.velocity('stop').velocity({
opacity: 1,
height: '75px'
},{
duration: 150,
display: 'block',
visibility: 'visible'
});
}
}
})
).append(
getLabledButton({
type: 'primary',
label: 'signature reader',
icon: 'fa-clipboard',
onClick: function(){
moduleElement.showSignatureReaderDialog(systemData);
}
})
).append(
getLabledButton({
type: 'default',
label: 'select all',
icon: 'fa-check-square',
onClick: function(){
var allRows = getRows(signatureTable);
var selectedRows = getSelectedRows(signatureTable);
var allRowElements = allRows.nodes().to$();
if(allRows.data().length === selectedRows.data().length){
allRowElements.removeClass('selected');
}else{
allRowElements.addClass('selected');
}
// check delete button
checkDeleteSignaturesButton();
}
})
).append(
getLabledButton({
type: 'danger',
classes: [config.sigTableClearButtonClass, 'pull-right'],
label: 'delete',
icon: 'fa-close',
badge: {
label: '0'
},
onClick: function(){
// delete all rows
var selectedRows = getSelectedRows(signatureTable);
bootbox.confirm('Delete ' + selectedRows.data().length + ' signature?', function(result) {
if(result){
deleteSignatures(selectedRows);
}
});
}
})
);
moduleElement.append(tableToolbar);
// add toolbar action for table -------------------------------------------------------------------------------
var tableToolbarAction = $('<div>', {
class: config.tableToolsActionClass
});
// create "empty table for new signature
var table = $('<table>', {
class: ['display', 'compact', 'nowrap', config.sigTableClass, config.sigTableSecondaryClass].join(' ')
});
tableToolbarAction.append(table);
tableToolbar.after(tableToolbarAction);
var signatureData = formatSignatureData(systemData, [emptySignatureData], emptySignatureOptions);
table.dataTable( {
data: signatureData,
paging: false,
ordering: false,
info: false,
searching: false
} );
table.makeEditable(systemData);
// scanned signatures progress bar ----------------------------------------------------------------------------
var moduleConfig = {
name: 'form/progress',
position: tableToolbar,
link: 'before'
};
var moduleData = {
label: true,
wrapperClass: config.signatureScannedProgressBarClass,
class: ['progress-bar-success'].join(' '),
percent: 0,
headline: 'System scanned',
headlineRight: ' ' // will be updated by js
};
Render.showModule(moduleConfig, moduleData);
// event listener for global "paste" signatures into the page -------------------------------------------------
$(document).off('paste').on('paste', function(e){
// do not read clipboard if pasting into form elements
if(
$(e.target).prop('tagName').toLowerCase() !== 'input' &&
$(e.target).prop('tagName').toLowerCase() !== 'textarea'
){
var clipboard = (e.originalEvent || e).clipboardData.getData('text/plain');
moduleElement.updateSignatureTableByClipboard(systemData, clipboard, {});
}
});
};
/**
* make a table or row editable
* @param systemData
*/
$.fn.makeEditable = function(systemData){
// table element OR row element
var tableElement = $(this);
// find editable fields
var sigNameFields = tableElement.find('.' + config.sigTableEditSigNameInput);
var sigGroupFields = tableElement.find('.' + config.sigTableEditSigGroupSelect);
var sigTypeFields = tableElement.find('.' + config.sigTableEditSigTypeSelect);
var sigDescriptionFields = tableElement.find('.' + config.sigTableEditSigDescriptionTextarea);
// jump to "next" editable field on save
var openNextEditDialogOnSave = function(fields){
fields.on('save', function(e, a){
var currentField = $(this);
setTimeout(function() {
var nextField = getNextEditableField(currentField);
nextField.editable('show');
// update scanning progressbar if sig "type" has changed AND
// the current field is in the "primary" table (not the "add" new sig row)
if(
$(e.target).hasClass(config.sigTableEditSigGroupSelect) &&
tableElement.hasClass(config.sigTablePrimaryClass)
){
currentField.parents('.' + config.moduleClass).updateScannedSignaturesBar({showNotice: true});
}
}, 200);
});
};
// helper function - get the next editable field in next table column
var getNextEditableField = function(field){
var nextEditableField = $(field).closest('td').next().find('.editable');
return $(nextEditableField);
};
/**
* add map/system specific data for each editable field in the sig-table
* @param params
* @returns {*}
*/
var modifyFieldParamsOnSend = function(params){
params.systemId = systemData.id;
return params;
};
// set global xEditable options for all table fields
$.extend($.fn.editable.defaults, {
url: Init.path.saveSignatureData,
dataType: 'json',
mode: 'popup',
container: 'body',
error: function(jqXHR, newValue){
var reason = '';
var status = '';
if(jqXHR.name){
// save error new sig (mass save)
reason = jqXHR.name;
status = 'Error';
}else{
reason = jqXHR.responseJSON.text;
status = jqXHR.status;
}
Util.showNotify({title: status + ': save signature', text: reason, type: 'error'});
$(document).setProgramStatus('problem');
return reason;
}
});
// Input sig name ---------------------------------------------------------------------------------------------
sigNameFields.editable({
type: 'text',
title: 'signature id',
name: 'name',
emptytext: '? ? ?',
validate: function(value) {
if($.trim(value).length < 3) {
return 'Id is less than min of "3"';
}else if($.trim(value).length > 10){
return 'Id is more than max of "10"';
}
},
params: modifyFieldParamsOnSend,
success: function(response, newValue){
if(response){
var signatureTypeField = $(this);
var rowElement = signatureTypeField.parents('tr');
var newRowData = response.signatures[0];
// update "updated" cell
updateSignatureCell(rowElement, 6, newRowData.updated);
}
}
});
// Select sig group (master) ----------------------------------------------------------------------------------
sigGroupFields.editable({
type: 'select',
title: 'group',
name: 'groupId',
emptytext: 'unknown',
onblur: 'submit',
showbuttons: false,
params: modifyFieldParamsOnSend,
source: function(){
var signatureGroupField = $(this);
var systemTypeId = parseInt( signatureGroupField.attr('data-systemTypeId') );
// get all available Signature Types
// json object -> "translate" keys to names
var availableTypes = Util.getSignatureGroupInfo('label');
// add empty option
availableTypes[0] = '';
return availableTypes;
},
success: function(response, newValue){
var signatureTypeField = $(this);
var rowElement = signatureTypeField.parents('tr');
newValue = parseInt(newValue);
if(response){
var newRowData = response.signatures[0];
// update "updated" cell
updateSignatureCell(rowElement, 6, newRowData.updated);
}
// find related "type" select (same row) and change options
var typeSelect = getNextEditableField(signatureTypeField);
var systemTypeId = parseInt( signatureTypeField.attr('data-systemTypeId') );
var areaId = parseInt( signatureTypeField.attr('data-areaid') );
var newSelectOptions = getAllSignatureNames(systemData, systemTypeId, areaId, newValue);
typeSelect.editable('option', 'source', newSelectOptions);
typeSelect.editable('setValue', null);
if(
newValue > 0 &&
newSelectOptions.length > 0
){
typeSelect.editable('enable');
}else{
typeSelect.editable('disable');
}
}
});
// Select sig type (slave: depends on sig type) ---------------------------------------------------------------
sigTypeFields.on('init', function(e, editable) {
// check if there are initial options available
var options = editable.input.options.source.bind(e.target)();
if(options.length <= 0){
editable.disable();
}
});
sigTypeFields.editable({ mode: 'popup',
type: 'select',
title: 'type',
name: 'typeId',
emptytext: 'unknown',
onblur: 'submit',
showbuttons: false,
params: modifyFieldParamsOnSend,
source: function(){
var signatureTypeField = $(this);
var systemTypeId = parseInt( signatureTypeField.attr('data-systemTypeId') );
var areaId = parseInt( signatureTypeField.attr('data-areaid') );
var groupId = parseInt( signatureTypeField.attr('data-groupId') );
var availableSigs = getAllSignatureNames(systemData, systemTypeId, areaId, groupId);
return availableSigs;
},
success: function(response, newValue){
if(response){
var signatureTypeField = $(this);
var rowElement = signatureTypeField.parents('tr');
var newRowData = response.signatures[0];
// update "updated" cell
updateSignatureCell(rowElement, 6, newRowData.updated);
}
}
});
// Textarea sig description -----------------------------------------------------------------------------------
sigDescriptionFields.editable({
type: 'textarea',
title: 'description',
name: 'description',
emptytext: '<i class="fa fa-fw fa-lg fa-pencil"></i>',
onblur: 'submit',
mode: 'inline',
showbuttons: false,
inputclass: config.editableDiscriptionInputClass,
params: modifyFieldParamsOnSend,
success: function(response, newValue){
if(response){
var signatureTypeField = $(this);
var rowElement = signatureTypeField.parents('tr');
var newRowData = response.signatures[0];
// update "updated" cell
updateSignatureCell(rowElement, 6, newRowData.updated);
}
}
});
// open even
sigDescriptionFields.on('shown', function(e, editable) {
// enlarge the tools-action container because the tables gets bigger
tableElement.parents('.' + config.tableToolsActionClass).css( 'height', '+=35px' );
});
// close event
sigDescriptionFields.on('hidden', function(e, editable) {
// enlarge the tools-action container because the tables gets bigger
tableElement.parents('.' + config.tableToolsActionClass).css( 'height', '-=35px' );
});
// open next field dialog -------------------------------------------------------------------------------------
openNextEditDialogOnSave(sigNameFields);
openNextEditDialogOnSave(sigGroupFields);
};
/**
* get all signatures that can exist for a given system
* @param systemData
* @param systemTypeId
* @param areaId
* @param groupId
* @returns {Array}
*/
var getAllSignatureNames = function(systemData, systemTypeId, areaId, groupId){
var newSelectOptions = [];
var cacheKey = [systemTypeId, areaId, groupId].join('_');
var newSelectOptionsCount = 0;
// check for cached signature names
if(sigNameCache.hasOwnProperty( cacheKey )){
// cached signatures do not include static WHs!
newSelectOptions = sigNameCache[cacheKey].slice(0);
newSelectOptionsCount = sumSignaturesRecursive('children', newSelectOptions);
}else{
// get new Options ----------
// get all possible "static" signature names by the selected groupId
var tempSelectOptions = Util.getAllSignatureNames(systemTypeId, areaId, groupId);
// format options into array with objects advantages: keep order, add more options (whs), use optgroup
if(tempSelectOptions){
var fixSelectOptions = [];
for (var key in tempSelectOptions) {
if (
key > 0 &&
tempSelectOptions.hasOwnProperty(key)
) {
newSelectOptionsCount++;
fixSelectOptions.push( {value: key, text: tempSelectOptions[key] } );
}
}
if(newSelectOptionsCount > 0){
if(groupId === 5){
// "wormhole" selected => multiple <optgroup> available
newSelectOptions.push({ text: 'Wandering WHs', children: fixSelectOptions});
}else{
newSelectOptions = fixSelectOptions;
}
}
}
// wormhole (cached signatures)
if( groupId === 5 ){
// add possible frigate holes
var frigateHoles = getFrigateHolesBySystem(areaId);
var frigateWHData = [];
for(var frigKey in frigateHoles){
if (
frigKey > 0 &&
frigateHoles.hasOwnProperty(frigKey)
) {
newSelectOptionsCount++;
frigateWHData.push( {value: newSelectOptionsCount, text: frigateHoles[frigKey]} );
}
}
if(frigateWHData.length > 0){
newSelectOptions.push({ text: 'Frigate WHs', children: frigateWHData});
}
// add possible incoming holes
var incomingWHData = [];
for(var incomingKey in Init.incomingWormholes){
if (
incomingKey > 0 &&
Init.incomingWormholes.hasOwnProperty(incomingKey)
) {
newSelectOptionsCount++;
incomingWHData.push( {value: newSelectOptionsCount, text: Init.incomingWormholes[incomingKey]} );
}
}
if(incomingWHData.length > 0){
newSelectOptions.push({ text: 'Incoming WHs', children: incomingWHData});
}
}else{
// groups without "children" (optgroup) should be sorted by "value"
// this is completely optional and not necessary!
newSelectOptions = newSelectOptions.sortBy('text');
}
// update cache (clone array) -> further manipulation to this array, should not be cached
sigNameCache[cacheKey] = newSelectOptions.slice(0);
}
// static wormholes (DO NOT CACHE) (not all C2 WHs have the same statics,...
if( groupId === 5 ){
// add static WH(s) for this system
if(systemData.statics){
var staticWHData = [];
for(var i = 0; i < systemData.statics.length; i++){
var staticWHName = systemData.statics[i].name + ' - ' + systemData.statics[i].security;
newSelectOptionsCount++;
staticWHData.push( {value: newSelectOptionsCount, text: staticWHName} );
}
if(staticWHData.length > 0){
newSelectOptions.unshift({ text: 'Static WHs', children: staticWHData});
}
}
}
// if selectOptions available -> add "empty" option as well
if(newSelectOptionsCount > 0){
newSelectOptions.unshift({ value: '0', text: ''});
}
return newSelectOptions;
};
/**
* recursive sum array.length for a given object key
* -> e.g.
* {
* first: {
* count = [4, 2, 1]
* test = { ... }
* },
* second: {
* count = [12, 13]
* test = { ... }
* }
* }
* -> sumSignaturesRecursive('count', obj) => 5;
* @param key
* @param obj
* @returns {number}
*/
var sumSignaturesRecursive = function(key, obj){
var sum = 0;
for (var prop in obj) {
if (obj.hasOwnProperty(prop) && key === prop) {
sum += obj[prop].length;
}
else if (Object.prototype.toString.call(obj[prop]) === '[object Object]') {
sum += sumSignaturesRecursive(key, obj[prop]);
}
}
return sum;
};
/**
* get possible frig holes that could spawn in a system
* filtered by "systemTypeId"
* @param systemTypeId
* @returns {{}}
*/
var getFrigateHolesBySystem = function(systemTypeId){
var signatureNames = {};
if(Init.frigateWormholes[systemTypeId]){
signatureNames = Init.frigateWormholes[systemTypeId];
}
return signatureNames;
};
/**
* deletes signature rows from signature table
* @param rows
*/
var deleteSignatures = function(rows){
var deletedSignatures = 0;
var moduleElement = $('.' + config.systemSigModuleClass);
var data = rows.data();
var signatureTableApi = signatureTable.api();
var rowElements = rows.nodes().to$();
var signatureCount = data.length;
var signatureIds = [];
for(var i = 0; i < data.length; i++){
signatureIds.push(data[i].id);
}
var requestData = {
signatureIds: signatureIds
};
// animation callback function
var removeCallback = function(rowElement){
// delete signature row
signatureTableApi.row(rowElement).remove().draw();
deletedSignatures++;
if(deletedSignatures === signatureCount){
// all animations finished
// update signature bar
moduleElement.updateScannedSignaturesBar({showNotice: false});
Util.showNotify({title: 'Signature deleted', text: signatureCount + ' signatures deleted', type: 'success'});
}
};
$.ajax({
type: 'POST',
url: Init.path.deleteSignatureData,
data: requestData,
dataType: 'json'
}).done(function(data){
for(var j = 0; j < rowElements.length; j++){
// removeRow
$(rowElements[j]).toggleTableRow(removeCallback);
}
}).fail(function( jqXHR, status, error) {
var reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': Delete signature', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
});
};
/**
* adds a new row to signature Table
* @param systemData
* @param signatureData
* @param animate
* @returns {*}
*/
var addSignatureRow = function(systemData, signatureData, animate){
var newSignatureData = formatSignatureData(systemData, [signatureData], fullSignatureOptions);
// insert new row in main signature table
var tablePrimaryElement = $('.' + config.sigTablePrimaryClass);
var dataTablePrimary = tablePrimaryElement.DataTable();
var newRowNode = dataTablePrimary.row.add(newSignatureData.shift()).draw().nodes();
var newRowElement = newRowNode.to$();
if(animate === true){
newRowElement.hide();
newRowElement.toggleTableRow(function(newRowElement){
// make new row editable
newRowElement.makeEditable(systemData);
// update scan progress bar
newRowElement.parents('.' + config.moduleClass).updateScannedSignaturesBar({showNotice: true});
});
}else{
newRowElement.makeEditable(systemData);
}
return newRowElement;
};
/**
* show/hides a table <tr> tag
* @param callback
*/
$.fn.toggleTableRow = function(callback){
var rowElement = $(this);
var cellElements = rowElement.children('td');
var duration = 100;
var tdCounter = 0;
// wrap each <td> into a container (for better animation performance)
// slideUp new wrapper divs
if(rowElement.is(':visible')){
// hide row
// stop sig counter by adding a stopClass to each <td>, remove padding
cellElements.addClass('stopCounter')
.velocity({
paddingTop: 0,
paddingBottom: 0
},{
duration: duration
}).wrapInner('<div>')
.children()
.css({
'willChange': 'height'
}).velocity('slideUp', {
duration: duration,
complete: function(animationElements){
tdCounter++;
// execute callback when last <td> animation finished
if(tdCounter === animationElements.length){
// remove wrapper
$(animationElements).children().unwrap();
if(callback !== undefined){
callback(rowElement);
}
}
}
});
}else{
// show row
// remove padding on "hidden" cells for smother animation
cellElements.css({
'padding-top': 0,
'padding-bottom': 0,
'willChange': 'padding-top, padding-top, height'
});
// add hidden wrapper for ea
cellElements.wrapInner($('<div>').hide());
// show row for padding animation
rowElement.show();
cellElements.velocity({
paddingTop: '4px',
paddingBottom: '4px'
},{
duration: duration,
queue: false,
complete: function(){
// animate <td> wrapper
cellElements.children()
.css({
'willChange': 'height'
}).velocity('slideDown', {
duration: duration,
complete: function(animationElements){
tdCounter++;
// execute callback when last <td> animation finished
if(tdCounter === animationElements.length){
// remove wrapper
var wrapperElements = cellElements.children();
for(var i = 0; i < wrapperElements.length; i++){
var currentWrapper = $(wrapperElements[i]);
if(currentWrapper.children().length > 0){
currentWrapper.children().unwrap();
}else{
currentWrapper.parent().html( currentWrapper.html() );
}
}
if(callback !== undefined){
callback(rowElement);
}
}
}
});
}
});
}
};
/**
* draw a signature table with data
* @param signatureData
* @param systemData
* @returns {*}
*/
$.fn.drawSignatureTable = function(signatureData, systemData){
var moduleElement = $(this);
// create new signature table ---------------------------------------------------------------------------------
var table = $('<table>', {
class: ['display', 'compact', 'nowrap', config.sigTableClass, config.sigTablePrimaryClass].join(' ')
});
moduleElement.append(table);
// create signature table and store the jquery object global for this module
signatureTable = table.dataTable( {
data: signatureData
} );
// make Table editable
signatureTable.makeEditable(systemData);
moduleElement.updateScannedSignaturesBar({showNotice: true});
return signatureTable;
};
/**
* format signature data array into dataTable structure
* @param systemData
* @param signatureData
* @param options
* @returns {Array}
*/
var formatSignatureData = function(systemData, signatureData, options){
var formattedData = [];
// security check
if(
systemData &&
systemData.id &&
systemData.id > 0
){
var systemTypeId = systemData.type.id;
// areaId is required as a key for signature names
// if areaId is 0, no signature data is available for this system
var areaId = Util.getAreaIdBySecurity(systemData.security);
for(var i = 0; i < signatureData.length; i++){
var data = signatureData[i];
var tempData = {};
// set id ---------------------------------------------------------------------------------------------
var sigId = 0;
if(data.id > 0){
sigId = data.id;
}
tempData.id = sigId;
// set status -----------------------------------------------------------------------------------------
var statusClass = '';
if(data.updated.character !== undefined){
statusClass = Util.getStatusInfoForCharacter(data.updated.character, 'class');
}
var status = '<i class="fa fa-fw fa-circle pf-user-status ' + statusClass + '"></i>';
tempData.status = {
status: status,
status_sort: statusClass
};
// set name -------------------------------------------------------------------------------------------
var sigName = '<a href="#" class="' + config.sigTableEditSigNameInput + '" ';
if(data.id > 0){
sigName += 'data-pk="' + data.id + '" ';
}
sigName += '>' + data.name + '</a>';
tempData.name = sigName;
// set group id ---------------------------------------------------------------------------------------
var sigGroup = '<a href="#" class="' + config.sigTableEditSigGroupSelect + '" ';
if(data.id > 0){
sigGroup += 'data-pk="' + data.id + '" ';
}
sigGroup += 'data-systemTypeId="' + systemTypeId + '" ';
sigGroup += 'data-areaId="' + areaId + '" ';
sigGroup += 'data-value="' + data.groupId + '" ';
sigGroup += '></a>';
tempData.group = {
group: sigGroup,
group_sort: data.groupId
};
// set type id ----------------------------------------------------------------------------------------
var sigType = '<a href="#" class="' + config.sigTableEditSigTypeSelect + '" ';
if(data.id > 0){
sigType += 'data-pk="' + data.id + '" ';
}
// set disabled if sig type is not selected
if(data.groupId < 1){
sigType += 'data-disabled="1" ';
}
sigType += 'data-systemTypeId="' + systemTypeId + '" ';
sigType += 'data-areaId="' + areaId + '" ';
sigType += 'data-groupId="' + data.groupId + '" ';
sigType += 'data-value="' + data.typeId + '" ';
sigType += '></a>';
tempData.type = sigType;
// set description ------------------------------------------------------------------------------------
var sigDescription = '<a href="#" class="' + config.sigTableEditSigDescriptionTextarea + '" ';
if(data.id > 0){
sigDescription += 'data-pk="' + data.id + '" ';
}
sigDescription += '>' + data.description + '</a>';
tempData.description = sigDescription;
// set created ----------------------------------------------------------------------------------------
tempData.created = data.created;
// set updated ----------------------------------------------------------------------------------------
tempData.updated = data.updated;
// info icon ------------------------------------------------------------------------------------------
var infoButton = '';
if(data.id > 0){
infoButton = '<i class="fa fa-fw fa-question-circle"></i>';
}
tempData.info = infoButton;
// action icon ----------------------------------------------------------------------------------------
var actionButton = '<i class="fa ' + options.actionClass + '"></i>';
tempData.action = {
action: options.action,
button: actionButton
};
formattedData.push(tempData);
}
}
return formattedData;
};
/**
* setup dataTable options for all signatureTables
* @param systemData
*/
var initSignatureDataTable = function(systemData){
$.extend( $.fn.dataTable.defaults, {
pageLength: -1,
lengthMenu: [[5, 10, 25, 50, -1], [5, 10, 25, 50, 'All']],
order: [1, 'asc'],
autoWidth: false,
responsive: {
details: false
},
language: {
emptyTable: 'No signatures added',
zeroRecords: 'No signatures found',
lengthMenu: 'Show _MENU_ signatures',
info: 'Showing _START_ to _END_ of _TOTAL_ signatures'
},
columnDefs: [
{
targets: 0,
orderable: true,
searchable: false,
title: '',
width: '10px',
class: ['text-center', 'min-tablet-l'].join(' '),
data: 'status',
type: 'html',
render: {
_: 'status',
sort: 'status_sort'
}
},{
targets: 1,
orderable: true,
searchable: true,
title: 'id',
type: 'html',
width: '30px',
data: 'name'
},{
targets: 2,
orderable: true,
searchable: false,
title: 'group',
type: 'html',
width: '50px',
data: 'group',
render: {
_: 'group',
sort: 'group_sort'
}
},{
targets: 3,
orderable: false,
searchable: false,
title: 'type',
type: 'html',
width: '180px',
data: 'type'
},{
targets: 4,
orderable: false,
searchable: false,
title: 'description',
type: 'html',
data: 'description'
},{
targets: 5,
title: 'created',
width: '90px',
searchable: false,
className: ['text-right', config.sigTableCounterClass, config.sigTableCreatedCellClass, 'min-tablet-l'].join(' '),
data: 'created',
render: {
_: 'created',
sort: 'created'
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
$(cell).initTimestampCounter();
}
},{
targets: 6,
title: 'updated',
width: '90px',
searchable: false,
className: ['text-right', config.sigTableCounterClass, config.sigTableUpdatedCellClass, 'min-tablet-l'].join(' '),
data: 'updated',
render: {
_: 'updated',
sort: 'updated'
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
$(cell).initTimestampCounter();
// highlight cell
var diff = Math.floor((new Date()).getTime()) - cellData.updated * 1000;
// age > 1 day
if( diff > 86400000){
$(cell).addClass('txt-color txt-color-warning');
}
}
},{
targets: 7,
title: '',
orderable: false,
searchable: false,
width: '10px',
class: 'pf-help text-center',
data: 'info',
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
if(rowData.id > 0){
// add row tooltip
var tooltipData = {
created: rowData.created,
updated: rowData.updated
};
$(cell).addCharacterInfoTooltip( tooltipData );
}
}
},{
targets: 8,
title: '',
orderable: false,
searchable: false,
width: '10px',
class: ['text-center', config.sigTableActionCellClass].join(' '),
data: 'action',
render: {
_: 'button',
sort: 'action'
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
var tempTableElement = this;
var rowElement = $(cell).parents('tr');
switch(cellData.action){
case 'add':
// add new signature ------------------------------------------------------------------
$(cell).on('click', function(e) {
// submit all fields within a table row
var formFields = rowElement.find('.editable');
// the "hide" makes sure to take care about open editable fields (e.g. description)
// otherwise, changes would not be submitted in this field (not necessary)
formFields.editable('hide');
// submit all xEditable fields
formFields.editable('submit', {
url: Init.path.saveSignatureData,
ajaxOptions: {
dataType: 'json', //assuming json response
beforeSend: function( xhr, settings ){
lockSignatureTable();
}
},
data: {
systemId: systemData.id, // additional data to submit
pk: 0 // new data no primary key
},
error: $.fn.editable.defaults.error, // user default xEditable error function
success: function (data, editableConfig) {
unlockSignatureTable(false);
var newRowElement = addSignatureRow(systemData, data.signatures[0], true);
// highlight
newRowElement.pulseTableRow('added');
// prepare "add signature" table for new entry -> reset -------------------
var signatureData = formatSignatureData(systemData, [emptySignatureData], emptySignatureOptions);
var dataSecondaryElement = $('.' + config.sigTableSecondaryClass);
var dataTableSecondary = dataSecondaryElement.DataTable();
var newAddRowElement = dataTableSecondary.clear().row.add(signatureData.shift()).draw().nodes();
newAddRowElement.to$().makeEditable(systemData);
Util.showNotify({
title: 'Signature added',
text: 'Name: ' + data.name,
type: 'success'
});
}
});
});
break;
case 'delete':
// delete signature -------------------------------------------------------------------
var confirmationSettings = {
container: 'body',
placement: 'left',
btnCancelClass: 'btn btn-sm btn-default',
btnCancelLabel: 'cancel',
btnCancelIcon: 'fa fa-fw fa-ban',
title: 'delete signature',
btnOkClass: 'btn btn-sm btn-danger',
btnOkLabel: 'delete',
btnOkIcon: 'fa fa-fw fa-close',
onConfirm : function(e, target){
var deleteRowElement = $(target).parents('tr');
var row = tempTableElement.DataTable().rows(deleteRowElement);
deleteSignatures(row);
}
};
// init confirmation dialog
$(cell).confirmation(confirmationSettings);
break;
}
}
}
],
createdRow: function(row, data, dataIndex){
},
initComplete: function(settings, json){
// table init complete
}
});
};
/**
* set module observer and look for relevant signature data to update
* @param moduleElement
*/
var setModuleObserver = function(moduleElement){
var tablePrimaryElement = $('.' + config.sigTablePrimaryClass);
var dataTablePrimary = signatureTable.DataTable();
var signatureTableApi = signatureTable.api();
$(document).off('pf:updateSystemModules').on('pf:updateSystemModules', function(e, data){
if(data.signatures){
moduleElement.updateSignatureTable(data.signatures, true);
}
});
// set multi row select ---------------------------------------------------------------------------------------
tablePrimaryElement.on('click', 'tr', function(e){
if(e.ctrlKey) {
$(this).toggleClass('selected');
// check delete button
checkDeleteSignaturesButton();
}
});
// draw event for signature table -----------------------------------------------------------------------------
signatureTableApi.on('draw.dt', function(){
// check delete button
checkDeleteSignaturesButton();
});
};
/**
* check the "delete signature" button. show/hide the button if a signature is selected
*/
var checkDeleteSignaturesButton = function(){
var selectedRows = getSelectedRows(signatureTable);
var selectedRowCount = selectedRows.data().length;
var clearButton = $('.' + config.sigTableClearButtonClass);
if(selectedRowCount > 0){
var allRows = getRows(signatureTable);
var rowCount = allRows.data().length;
var badgetText = selectedRowCount;
if(selectedRowCount >= rowCount){
badgetText = 'all';
}
clearButton.find('.badge').text( badgetText );
// update clear signatures button text
clearButton.velocity('stop');
if( clearButton.is(':hidden') ){
// show button
clearButton.velocity('transition.bounceIn', {
duration: 180
});
}else{
// highlight button
clearButton.velocity('callout.pulse', {
duration: 240
});
}
}else{
// hide button
clearButton.velocity('transition.bounceOut', {
duration: 180
});
}
};
/**
* get all rows of a table
* @param table
* @returns {*}
*/
var getRows = function(table){
var tableApi = table.api();
var rows = tableApi.rows();
return rows;
};
/**
* get all selected rows of a table
* @param table
* @returns {*}
*/
var getSelectedRows = function(table){
var tableApi = table.api();
var selectedRows = tableApi.rows('.selected');
return selectedRows;
};
/**
* get module element
* @param parentElement
* @param systemData
* @returns {*|HTMLElement}
*/
var getModule = function(parentElement, systemData){
// create new module container
var moduleElement = $('<div>', {
class: [config.moduleClass, config.systemSigModuleClass].join(' '),
css: {opacity: 0}
});
// headline
var headline = $('<h5>', {
text: 'Signatures'
});
moduleElement.append(headline);
$(parentElement).append(moduleElement);
// init dataTables
initSignatureDataTable(systemData);
// draw "new signature" add table -----------------------------------------------------------------------------
moduleElement.drawSignatureTableToolbar(systemData);
// request signature data for system --------------------------------------------------------------------------
var requestData = {
systemIds: [systemData.id]
};
$.ajax({
type: 'POST',
url: Init.path.getSignatures,
data: requestData,
dataType: 'json'
}).done(function(signatureData){
var signatureTableData = formatSignatureData(systemData, signatureData, fullSignatureOptions);
// draw signature table
moduleElement.drawSignatureTable(signatureTableData, systemData);
// set module observer
setModuleObserver(moduleElement);
}).fail(function( jqXHR, status, error) {
var reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': Get signatures', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
});
return moduleElement;
};
/**
* main module load function
* @param systemData
*/
$.fn.drawSignatureTableModule = function(systemData){
var parentElement = $(this);
// show module
var showModule = function(moduleElement){
if(moduleElement){
moduleElement.velocity('transition.slideDownIn', {
duration: Init.animationSpeed.mapModule,
delay: Init.animationSpeed.mapModule,
complete: function(){
unlockSignatureTable(true);
}
});
}
};
// some custom array functions
var initArrayFunctions = function(){
/**
* sort array of objects by property name
* @param p
* @returns {Array.<T>}
*/
Array.prototype.sortBy = function(p) {
return this.slice(0).sort(function(a,b) {
return (a[p] > b[p]) ? 1 : (a[p] < b[p]) ? -1 : 0;
});
};
};
// check if module already exists
var moduleElement = parentElement.find('.' + config.systemSigModuleClass);
if(moduleElement.length > 0){
// disable update
lockSignatureTable();
moduleElement.velocity('transition.slideDownOut', {
duration: Init.animationSpeed.mapModule,
complete: function(tempElement){
// Destroying the data tables throws
// save remove of all dataTables
signatureTable.api().destroy();
$(tempElement).remove();
moduleElement = getModule(parentElement, systemData);
// make modules appear "nice"
moduleElement.delay(150);
showModule(moduleElement);
}
});
}else{
// init array prototype functions
initArrayFunctions();
moduleElement = getModule(parentElement, systemData);
showModule(moduleElement);
}
};
});