/**
* form elements
*/
define([
'jquery',
'app/init',
'app/util',
'app/map/util'
], ($, Init, Util, MapUtil) => {
'use strict';
let config = {
// Select2
resultOptionImageClass: 'pf-result-image', // class for Select2 result option entry with image
select2ImageLazyLoadClass: 'pf-select2-image-lazyLoad' // class for Select2 result images that should be lazy loaded
};
/**
* format result data
* @param data
* @returns {*}
*/
let formatCategoryTypeResultData = data => {
if(data.loading) return data.text;
if(data.placeholder) return data.placeholder;
let markup = `
`;
if(data.hasOwnProperty('children')){
// category group label
markup += `
${data.text}
`;
markup += `
(${data.children.length})
`;
}else{
let imagePath = '';
let iconName = '';
let thumb = '';
switch(data.categoryType){
case 'character':
imagePath = Util.eveImageUrl('characters', data.id);
break;
case 'corporation':
imagePath = Util.eveImageUrl('corporations', data.id);
break;
case 'alliance':
imagePath = Util.eveImageUrl('alliances', data.id);
break;
case 'inventoryType':
imagePath = Util.eveImageUrl('types', data.id);
break;
case 'render':
imagePath = Util.eveImageUrl('types', data.id);
break;
case 'station':
iconName = 'fa-home';
break;
case 'system':
iconName = 'fa-sun';
break;
}
if(imagePath){
thumb = `
}svg/logo_simple.svg)
`;
}else if(iconName){
thumb = `
`;
}
markup += `
${thumb}
`;
markup += `
${data.text}
`;
}
markup += '
';
return markup;
};
/**
* format results data for signature type select
* @param state
* @param container
* @param customOptions
* @returns {*|k.fn.init|jQuery|HTMLElement}
*/
let formatSignatureTypeSelectionData = (state, container, customOptions) => {
let parts = state.text.split(' - ');
let markup = '';
if(parts.length === 2){
// wormhole data -> 2 columns
let name = parts[0];
let sizeLabel;
if(Util.getObjVal(customOptions, 'showWhSizeLabel')){
sizeLabel = Util.getObjVal(Init, `wormholes.${name}.size.label`) || '';
}
let securityClass = Util.getSecurityClassForSystem(getSystemSecurityFromLabel(parts[1]));
// some labels have a "suffix" label that should not have the securityClass
let labelParts = parts[1].split(/\s(.+)/);
let label = labelParts[0];
let suffix = labelParts[1] ? labelParts[1] : '';
let classes = [securityClass, Util.config.popoverTriggerClass, Util.config.helpDefaultClass];
markup += `${name}`;
if(sizeLabel !== undefined){
markup += `${sizeLabel}`;
}
markup += '';
markup += ` ${label}`;
if(suffix.length){
markup += ` ${suffix}`;
}
}else{
markup += `${state.text}`;
}
return $(markup);
};
/**
* format result data for signature type OR signature connection data
* @param data
* @param formatType
* @returns {*}
*/
let formatSignatureTypeConnectionResultData = (data, formatType) => {
if(data.loading) return data.text;
if(data.placeholder) return data.placeholder;
let markup = '';
if(data.hasOwnProperty('children')){
// optgroup label
markup += '
' + data.text + '
';
markup += '
(' + data.children.length + ')
';
}else{
// child label
let parts = data.text.split(' - ');
if(parts.length === 2){
// wormhole data -> 2 columns
let securityClass = Util.getSecurityClassForSystem(getSystemSecurityFromLabel(parts[1]));
switch(formatType){
case 'wormhole':
// some labels have a "suffix" label that should not have the securityClass
let labelParts = parts[1].split(/\s(.+)/);
let label = labelParts[0];
let suffix = labelParts[1] ? labelParts[1] : '';
markup += '
' + parts[0] + '
';
markup += '
';
markup += '
' + label + '
';
markup += '
' + suffix + '
';
break;
case 'system':
markup += '
' + parts[0] + '
';
markup += '
' + parts[1] + '
';
break;
}
}else{
markup += '
' + data.text + '
';
}
}
markup += '
';
return $(markup);
};
/**
* format results data for signature connection select
* @param state
* @returns {*|jQuery|HTMLElement}
*/
let formatSignatureConnectionSelectionData = state => {
let parts = state.text.split(' - ');
let markup = '';
if(parts.length === 2){
// wormhole data -> 2 columns
let styleClass = ['pf-fake-connection-text'];
if(state.metaData){
let metaData = state.metaData;
if(metaData.type){
let type = metaData.type;
if(type.includes('wh_eol')){
styleClass.push('pf-wh-eol');
}
if(type.includes('wh_reduced')){
styleClass.push('pf-wh-reduced');
}
if(type.includes('wh_critical')){
styleClass.push('pf-wh-critical');
}
if(type.includes('wh_jump_mass_s')){
styleClass.push('pf-wh-frig');
}
}
}
let securityClass = Util.getSecurityClassForSystem(parts[1]);
markup += `${parts[0]}`;
markup += ` ${parts[1]}`;
}else{
markup += `${state.text}`;
}
return $(markup);
};
/**
* try to parse a security label into security name
* -> "C1/2/3 (unknown)" -> C1
* "C3" -> C3
* "H" -> H
* "0.0" -> 0.0
* "C12 Thera" -> C12
* @param security
* @returns {string}
*/
let getSystemSecurityFromLabel = security => {
let matches = security.match(/^(\w+\.?\w?)/i);
return matches ? matches[1] : '';
};
/**
* init a select element as "select2" for map selection
*/
$.fn.initMapSelect = function(){
let selectElement = $(this);
$.when(
selectElement.select2({
dropdownParent: selectElement.parents('.modal-body'),
maximumSelectionLength: 5
})
);
};
/**
* init a select element as "select2" for connection size types
* @param options
*/
$.fn.initConnectionSizeSelect = function(options){
let selectElement = $(this);
let defaultConfig = {
dropdownParent: selectElement.parents('.modal-body'),
minimumResultsForSearch: Infinity,
width: '100%',
maxSelectionLength: 1
};
options = $.extend({}, defaultConfig, options);
let formatConnectionSizeResultData = data => {
if(data.loading) return data.text;
if(data.placeholder) return data.placeholder;
let connectionClass = MapUtil.getConnectionInfo(data.text, 'cssClass');
let label = Util.getObjVal(Init.wormholeSizes, data.text + '.label') || '?';
let text = Util.getObjVal(Init.wormholeSizes, data.text + '.text') || 'all';
let markup = '';
markup += '
';
markup += '';
markup += '
';
markup += '
';
markup += '
';
markup += '
';
markup += '
';
markup += text;
markup += '
';
markup += '
';
return $(markup);
};
options.templateSelection = formatConnectionSizeResultData;
options.templateResult = formatConnectionSizeResultData;
$.when(
selectElement.select2(options)
);
};
/**
* init a sselect element as "select2" for "status" selection
* @param options
* @returns {*}
*/
$.fn.initStatusSelect = function(options){
let defaultConfig = {
minimumResultsForSearch: Infinity,
width: '100%',
iconClass: 'fa-circle'
};
options = $.extend({}, defaultConfig, options);
let formatStatusSelectionData = state => {
return ' ' + state.text;
};
let formatStatusResultData = data => {
if(data.loading) return data.text;
if(data.placeholder) return data.placeholder;
let markup = '';
markup += '
';
markup += '';
markup += '
';
markup += '
' + data.text + '
';
markup += '
';
return $(markup);
};
options.templateSelection = formatStatusSelectionData;
options.templateResult = formatStatusResultData;
return this.each(function(){
let selectElement = $(this);
selectElement.select2(options);
});
};
/**
* init a select element as an ajax based "select2" object for system search
* @param options
*/
$.fn.initSystemSelect = function(options){
let selectElement = $(this);
let defaultConfig = {
maxSelectionLength: 1
};
options = $.extend({}, defaultConfig, options);
let shatteredClass = Util.getSecurityClassForSystem('SH');
// format result data
function formatResultData (data){
if(data.loading) return data.text;
// abyss system font
let systemNameClass = data.security === 'A' ? Util.config.fontTriglivianClass : '';
// show effect info just for wormholes
let hideEffectClass = data.effect === null ? 'hide' : '';
let hideShatteredClass = !data.shattered ? 'hide' : '';
let markup = '';
markup += '
' + data.text + '
';
markup += '
';
markup += '';
markup += '
';
markup += '
' + data.security + '
';
markup += '
';
markup += '';
markup += '
';
markup += '
' + data.trueSec + '
';
return markup;
}
$.when(
selectElement.select2({
ajax: {
url: params => `${Init.path.api}/SystemSearch/${params.term.trim()}`,
dataType: 'json',
delay: 250,
timeout: 5000,
cache: true,
data: params => ({
page: params.page || 1
}),
processResults: function(data, params){
// parse the results into the format expected by Select2.
return {
results: data.results.map( function(item){
// "id" or "name"
let id = item[options.key];
let disabled = false;
let trueSec = parseFloat(item.trueSec);
let secClass = Util.getSecurityClassForSystem(item.security);
let trueSecClass = Util.getTrueSecClassForSystem(trueSec);
let effectClass = MapUtil.getEffectInfoForSystem(item.effect, 'class');
// check if system is dialed
if(
options.disabledOptions &&
options.disabledOptions.indexOf(parseInt(id, 10)) !== -1
){
disabled = true;
}
// "fix" security level
if(
trueSec > 0 &&
trueSec < 0.1
){
trueSec = 0.1;
}else{
trueSec = Math.round(trueSec * 10) / 10;
}
return {
id: id,
text: item.name,
// systemId: parseInt(item.systemId),
security: item.security,
secClass: secClass,
trueSec: trueSec.toFixed(1),
trueSecClass: trueSecClass,
effect: item.effect,
effectClass: effectClass,
shattered: item.shattered,
disabled: disabled
};
}),
pagination: {
more: data.pagination.more
}
};
},
error: function(jqXHR, status, error){
if( !Util.isXHRAborted(jqXHR) ){
let reason = status + ' ' + jqXHR.status + ': ' + error;
Util.showNotify({title: 'System select warning', text: reason + ' deleted', type: 'warning'});
}
}
},
dropdownParent: selectElement.parents('.modal-body'),
minimumInputLength: 3,
templateResult: formatResultData,
placeholder: 'Name or ID',
allowClear: true,
maximumSelectionLength: options.maxSelectionLength
}).on('change', function(e){
// select changed
if(options.onChange){
options.onChange(parseInt($(this).val()) || 0);
}
}).on('select2:open', function(){
// clear selected system (e.g. default system)
// => improves usability (not necessary). There is a small "x" if field can be cleared manually
if(
options.maxSelectionLength === 1 &&
$(this).val() !== null
){
$(this).val('').trigger('change');
}
})
).done(function(a,b){
// open select if not already pre-selected
if($(this).val() === null){
selectElement.select2('open');
}
});
};
/**
* init a select element as an ajax based "select2" object for Access resources
* character (private map), corporation (corp map), alliance (ally map)
* @param options
*/
$.fn.initAccessSelect = function(options){
let formatSelectionData = data => {
if(data.loading) return data.text;
let markup = '';
markup += '
' + data.text + '
';
return markup;
};
return this.each(function(){
let selectElement = $(this);
$.when(
selectElement.select2({
ajax: {
url: function(params){
// add params to URL
return Init.path.searchAccess + '/' + options.type + '/' + params.term;
},
dataType: 'json',
delay: 250,
timeout: 5000,
cache: true,
data: function(params){
// no url params here
return;
},
processResults: function(data, page){
// parse the results into the format expected by Select2.
return {
results: data.map( function(item){
return {
id: item.id,
text: item.name,
categoryType: options.type
};
})
};
},
error: function(jqXHR, status, error){
if( !Util.isXHRAborted(jqXHR) ){
let reason = status + ' ' + jqXHR.status + ': ' + error;
Util.showNotify({title: 'Access select warning', text: reason + ' deleted', type: 'warning'});
}
}
},
dropdownParent: selectElement.parents('.modal-body'),
minimumInputLength: 3,
placeholder: options.type + ' names',
allowClear: false,
maximumSelectionLength: options.maxSelectionLength,
templateResult: formatCategoryTypeResultData,
templateSelection: formatSelectionData
}).on('change', function(e){
// select changed
})
).done(function(){
// after init finish
});
});
};
/**
* init a select element as an ajax based "select2" object for universeTypes
* e.g. 'alliance', 'corporation', 'character', ...
* @param options
* @returns {*}
*/
$.fn.initUniverseSearch = function(options){
let showErrorNotification = (reason) => {
Util.showNotify({title: 'Search failed', text: reason + ' deleted', type: 'warning'});
};
/**
* format selection data
* @param data
* @returns {*}
*/
function formatSelectionData (data){
if(data.loading) return data.text;
if(data.placeholder) return data.placeholder;
let markup = '';
markup += '
' + data.text + '
';
return markup;
}
/**
* sort universe data
* @param data universe search result array
* @param term search term
*/
function sortResultData (data, term){
let levenshtein = (a,b) => {
let matrix = new Array(a.length+1);
for(let i = 0; i < matrix.length; i++){
matrix[i] = new Array(b.length+1).fill(0);
}
for(let ai = 1; ai <= a.length; ai++){
matrix[ai][0] = ai;
}
for(let bi = 1; bi <= b.length; bi++){
matrix[0][bi] = bi;
}
for(let bi = 1; bi <= b.length; bi++){
for(let ai = 1; ai <= a.length; ai++){
matrix[ai][bi] = Math.min(
matrix[ai-1][bi]+1,
matrix[ai][bi-1]+1,
matrix[ai-1][bi-1]+(a[ai-1] === b[bi-1] ? 0 : 1)
);
}
}
return matrix[a.length][b.length];
};
data.sort((a,b) => {
let levA = levenshtein(term, a.name.toLowerCase());
let levB = levenshtein(term, b.name.toLowerCase());
return levA === levB ? 0 : (levA > levB ? 1 : -1);
});
}
return this.each(function(){
let selectElement = $(this);
$.when(
selectElement.select2({
ajax: {
type: 'POST',
url: function(params){
// add params to URL
return Init.path.searchUniverseData + '/' + encodeURI(params.term);
},
dataType: 'json',
delay: 250,
timeout: 5000,
cache: true,
data: function(params){
return {
categories: options.categoryNames
};
},
processResults: function(result, page){
let data = {results: []};
if(result.hasOwnProperty('error')){
showErrorNotification(result.error);
}else{
let mapChildren = function(item){
return {
id: item.id,
text: item.name,
categoryType: this
};
};
for(let category in result){
// skip custom functions in case result = [] (array functions)
if(result.hasOwnProperty(category)){
// sort results (optional)
sortResultData(result[category], page.term);
data.results.push({
text: category,
children: result[category].map(mapChildren, category)
});
}
}
}
return data;
},
error: function(jqXHR, status, error){
if( !Util.isXHRAborted(jqXHR) ){
let reason = status + ' ' + jqXHR.status + ': ' + error;
showErrorNotification(reason);
}
}
},
dropdownParent: selectElement.parents('.modal-body') ,
minimumInputLength: 3,
placeholder: '',
/* alphabetic search not always fits the users need
sorter: data => {
// sort nested data options by "text" prop
return data.map((group, index) => {
group.children = group.children.sort((a,b) => a.text.localeCompare(b.text) );
return group;
});
},*/
disabled: options.hasOwnProperty('disabled') ? options.disabled : false,
allowClear: options.maxSelectionLength <= 1,
maximumSelectionLength: options.maxSelectionLength,
templateResult: formatCategoryTypeResultData
// templateSelection: formatSelectionData, // some issues with "clear" selection on single selects (empty option is needed)
}).on('change', function(e){
// select changed
}).on('select2:open', function(){
// clear selected system (e.g. default system)
// => improves usability (not necessary). There is a small "x" if field can be cleared manually
if(
options.maxSelectionLength === 1 &&
$(this).val() !== null
){
$(this).val('').trigger('change');
}
})
).done(function(){
// after init finish
});
});
};
/**
* init a select element as an "select2" object for system search
* @param options
* @returns {*}
*/
$.fn.initUniverseTypeSelect = function(options){
/**
* get select option data by categoryIds
* @param categoryIds
* @returns {Array}
*/
let getOptionsData = categoryIds => {
let data = [];
let mapChildren = function(type){
return {
id: type.id,
text: type.name,
mass: type.hasOwnProperty('mass') ? type.mass : null,
groupId: this.groupId,
categoryId: this.categoryId,
categoryType: this.categoryType
};
};
for(let categoryId of categoryIds){
let categoryData = Util.getObjVal(Init, 'universeCategories.' + categoryId);
if(categoryData && categoryData.groups){
// categoryId data exists and has groups...
for(let groupData of categoryData.groups){
if(groupData && groupData.types){
// groupData exists and has types...
data.push({
text: groupData.name,
children: groupData.types.map(mapChildren, {
groupId: groupData.id,
categoryId: categoryData.id,
categoryType: 'inventoryType',
})
});
}
}
}
}
return data;
};
return this.each(function(){
let selectElement = $(this);
$.when(
selectElement.select2({
data: getOptionsData(options.categoryIds),
dropdownParent: selectElement.parents('.modal-body'),
minimumInputLength: 0, // minimum number of characters required to start a search
maximumInputLength: 100, // maximum number of characters that may be provided for a search term
placeholder: '',
allowClear: options.maxSelectionLength <= 1,
multiple: options.maxSelectionLength > 1,
maximumSelectionLength: options.maxSelectionLength,
// maximumSelectionLength: options.maxSelectionLength > 1 ? options.maxSelectionLength > 1 : 0,
// minimumResultsForSearch: 5, // minimum number of results required to display the search box
templateResult: formatCategoryTypeResultData
}).on('select2:open', function(){
// clear selected system (e.g. default system)
// => improves usability (not necessary). There is a small "x" if field can be cleared manually
if(
options.maxSelectionLength === 1 &&
$(this).val() !== null
){
$(this).val('').trigger('change');
}
}).val(options.selected).trigger('change')
);
});
};
/**
* init a select element as an "select2" object for signature group data
* @param options
* @returns {*}
*/
$.fn.initSignatureGroupSelect = function(options){
let defaultConfig = {
minimumResultsForSearch: Infinity,
width: '110px',
dropdownParent: this.parents('.popover-content')
};
options = $.extend({}, defaultConfig, options);
return this.each(function(){
let selectElement = $(this);
selectElement.select2(options);
// initial open dropDown
if( !parseInt(selectElement.val()) ){
// setTimeout() required because of dropDown positioning
setTimeout(() => {
selectElement.select2('open');
}, 0);
}
});
};
/**
* init a select element as an "select2" object for signature types data
* @param options
* @param hasOptGroups
* @returns {*}
*/
$.fn.initSignatureTypeSelect = function(options, hasOptGroups){
let defaultConfig = {
minimumResultsForSearch: 10,
width: '220px',
dropdownParent: this.parents('.popover-content')
};
options = $.extend({}, defaultConfig, options);
let formatSignatureTypeResultData = data => {
return formatSignatureTypeConnectionResultData(data, 'wormhole');
};
let search = (params, data) => {
if($.trim(params.term) === '') return data; // If there are no search terms, return all of the data
if(typeof data.children === 'undefined') return null; // Skip if there is no 'children' property
// `data.children` contains the actual options that we are matching against
let filteredChildren = [];
for(let [idx, child] of Object.entries(data.children)){
if(child.text.toUpperCase().indexOf(params.term.toUpperCase()) === 0){
filteredChildren.push(child);
}
}
// If we matched any of the timezone group's children, then set the matched children on the group
// and return the group object
if(filteredChildren.length){
let modifiedData = $.extend({}, data, true);
modifiedData.children = filteredChildren;
// You can return modified objects from here
// This includes matching the `children` how you want in nested data sets
return modifiedData;
}
// Return `null` if the term should not be displayed
return null;
};
options.templateSelection = formatSignatureTypeSelectionData;
options.templateResult = formatSignatureTypeResultData;
if(hasOptGroups){
// NOT nested selects don“t need the custom search() function
options.matcher = search;
}
return this.each(function(){
let selectElement = $(this);
selectElement.select2(options);
// initial open dropDown
if( !parseInt(selectElement.val()) ){
// setTimeout() required because of dropDown positioning
setTimeout(() => {
selectElement.select2('open');
}, 0);
}
});
};
/**
* init a select element as an "select2" object for signature group data
* @param options
* @returns {*}
*/
$.fn.initSignatureConnectionSelect = function(options){
let defaultConfig = {
minimumResultsForSearch: Infinity,
width: '140px',
dropdownParent: this.parents('.popover-content')
};
options = $.extend({}, defaultConfig, options);
let formatSignatureConnectionResultData = data => {
return formatSignatureTypeConnectionResultData(data, 'system');
};
options.templateSelection = formatSignatureConnectionSelectionData;
options.templateResult = formatSignatureConnectionResultData;
return this.each(function(){
let selectElement = $(this);
// remove existing from DOM in case "data" is explicit set
if(options.data){
selectElement.empty();
}
selectElement.select2(options);
// initial open dropDown
if( !parseInt(selectElement.val()) ){
// setTimeout() required because of dropDown positioning
setTimeout(() => {
selectElement.select2('open');
}, 0);
}
});
};
return {
formatSignatureTypeSelectionData: formatSignatureTypeSelectionData,
formatSignatureConnectionSelectionData: formatSignatureConnectionSelectionData
};
});