Difference between revisions of "MediaWiki:Gadget-calculator-core.js"
From WikiAnesthesia
Chris Rishel (talk | contribs) |
Chris Rishel (talk | contribs) |
||
| Line 912: | Line 912: | ||
var isTable = this.tagName.toLowerCase() === 'tr'; | var isTable = this.tagName.toLowerCase() === 'tr'; | ||
var $infoButton = null; | |||
if( calculation.hasInfo() ) { | |||
$infoButton = $( '<a>', { | |||
'data-toggle': 'collapse', | |||
href: '#collapseInfo', | |||
role: 'button', | |||
'aria-expanded': 'false', | |||
'aria-controls': calculation.getContainerClass() + '-info' | |||
} ) | |||
.append( $( '<i>', { | |||
class: 'far fa-question-circle' | |||
} ) ); | |||
} | |||
if( isTable ) { | if( isTable ) { | ||
| Line 924: | Line 939: | ||
if( calculation.hasInfo() ) { | if( calculation.hasInfo() ) { | ||
$( this ) | $( this ) | ||
.append( $( '<td>', { | .append( $( '<td>', { | ||
| Line 935: | Line 946: | ||
} else { | } else { | ||
$( this ) | $( this ) | ||
.append( calculation.getLabelHtml() + ': ' + valueString ); | .append( calculation.getLabelHtml() + $infoButton + ': ' + valueString ); | ||
} | } | ||
Revision as of 23:43, 31 July 2021
/**
* @author Chris Rishel
*/
( function() {
const COOKIE_EXPIRATION = 12 * 60 * 60;
const TYPE_NUMBER = 'number';
const TYPE_STRING = 'string';
const VALID_TYPES = [
TYPE_NUMBER,
TYPE_STRING
];
// Polyfill to fetch unit's base. This may become unnecessary in a future version of math.js
math.Unit.prototype.getBase = function() {
for( var iBase in math.Unit.BASE_UNITS ) {
if( this.equalBase( math.Unit.BASE_UNITS[ iBase ] ) ) {
return iBase;
}
}
return null;
};
mw.calculators = {
calculators: {},
calculations: {},
objectClasses: {},
units: {},
unitsBases: {},
variables: {},
addCalculations: function( calculationData ) {
var calculations = mw.calculators.createCalculatorObjects( 'CalculatorCalculation', calculationData );
for( var calculationId in calculations ) {
var calculation = calculations[ calculationId ];
mw.calculators.calculations[ calculationId ] = calculation;
var inputCalculations = calculation.data.calculations.required.concat( calculation.data.calculations.optional );
for( var iInputCalculation in inputCalculations ) {
var inputCalculationId = inputCalculations[ iInputCalculation ];
if( !mw.calculators.calculations.hasOwnProperty( inputCalculationId ) ) {
throw new Error('Calculation "' + inputCalculationId + '" does not exist for calculation "' + calculationId + '"');
}
mw.calculators.calculations[ inputCalculationId ].addCalculation( calculationId );
}
var inputVariables = calculation.data.variables.required.concat( calculation.data.variables.optional );
for( var iInputVariable in inputVariables ) {
var inputVariableId = inputVariables[ iInputVariable ];
if( !mw.calculators.variables.hasOwnProperty( inputVariableId ) ) {
throw new Error('Variable "' + inputVariableId + '" does not exist for calculation "' + calculationId + '"');
}
mw.calculators.variables[ inputVariableId ].addCalculation( calculationId );
}
}
},
addCalculators: function( moduleId, calculatorData ) {
var calculators = mw.calculators.createCalculatorObjects( 'CalculatorCalculator', calculatorData );
if( !mw.calculators.calculators.hasOwnProperty( moduleId ) ) {
mw.calculators.calculators[ moduleId ] = {};
}
for( var calculatorId in calculators ) {
mw.calculators.calculators[ moduleId ][ calculatorId ] = calculators[ calculatorId ];
mw.calculators.calculators[ moduleId ][ calculatorId ].render();
}
},
addUnitsBases: function( unitsBaseData ) {
var unitsBases = mw.calculators.createCalculatorObjects( 'CalculatorUnitsBase', unitsBaseData );
for( var unitsBaseId in unitsBases ) {
mw.calculators.unitsBases[ unitsBaseId ] = unitsBases[ unitsBaseId ];
}
},
addUnits: function( unitsData ) {
var units = mw.calculators.createCalculatorObjects( 'CalculatorUnits', unitsData );
for( var unitsId in units ) {
if( mw.calculators.units.hasOwnProperty( unitsId ) ) {
continue;
}
// CalculatorUnits class variables directly support the options object format for math.createUnit
math.createUnit( unitsId, {
aliases: units[ unitsId ].aliases,
baseName: units[ unitsId ].baseName,
definition: units[ unitsId ].definition,
prefixes: units[ unitsId ].prefixes,
offset: units[ unitsId ].offset,
} );
mw.calculators.units[ units ] = units[ unitsId ];
}
},
addVariables: function( variableData ) {
var variables = mw.calculators.createCalculatorObjects( 'CalculatorVariable', variableData );
for( var varId in variables ) {
var variable = variables[ varId ];
var cookieValue = mw.calculators.getCookieValue( varId );
if( cookieValue ) {
variable.setValue( cookieValue );
}
mw.calculators.variables[ varId ] = variable;
}
},
createCalculatorObjects: function( className, objectData ) {
if( !mw.calculators.objectClasses.hasOwnProperty( className ) ) {
throw new Error( 'Invalid class name "' + className + '"' );
}
var objects = {};
for( var objectId in objectData ) {
var propertyValues = objectData[ objectId ];
propertyValues.id = objectId;
objects[ objectId ] = new mw.calculators.objectClasses[ className ]( propertyValues );
}
return objects;
},
createInputGroup: function( variableIds ) {
var $form = $( '<form>', {
} );
var $formRow = $( '<div>', {
class: 'form-row'
} ).css( 'flex-wrap', 'nowrap' );
for( var iVariableId in variableIds ) {
var variableId = variableIds[ iVariableId ];
if( !mw.calculators.variables.hasOwnProperty( variableId ) ) {
throw new Error( 'Invalid variable name "' + variableId + '"' );
}
$formRow.append( mw.calculators.variables[ variableId ].createInput() );
}
return $form.append( $formRow );
},
getCookieKey: function( varId ) {
return 'calculators-var-' + varId;
},
getCookieValue: function( varId ) {
var cookieValue = mw.cookie.get( mw.calculators.getCookieKey( varId ) );
if( !cookieValue ) {
return null;
}
return cookieValue;
},
getCalculation: function( calculationId ) {
if( mw.calculators.calculations.hasOwnProperty( calculationId ) ) {
return mw.calculators.calculations[ calculationId ];
} else {
return null;
}
},
getCalculator: function( moduleId, calculatorId ) {
if( mw.calculators.calculators.hasOwnProperty( moduleId ) &&
mw.calculators.calculators[ moduleId ].hasOwnProperty( calculatorId ) ) {
return mw.calculators.calculators[ moduleId ][ calculatorId ];
} else {
return null;
}
},
getUnitsString: function( value ) {
if( typeof value !== 'object' ) {
return null;
}
var units = value.formatUnits().replace( /\s/g, '' );
var unitsBase = value.getBase();
if( mw.calculators.unitsBases.hasOwnProperty( unitsBase ) &&
typeof mw.calculators.unitsBases[ unitsBase ].toString === 'function' ) {
units = mw.calculators.unitsBases[ unitsBase ].toString( units );
}
return units;
},
getVariable: function( varId ) {
if( mw.calculators.variables.hasOwnProperty( varId ) ) {
return mw.calculators.variables[ varId ];
} else {
return null;
}
},
getValue: function( varId ) {
if( mw.calculators.variables.hasOwnProperty( varId ) ) {
return mw.calculators.variables[ varId ].getValue();
} else {
return null;
}
},
init: function() {
$( '.calculator' ).each( function() {
var gadgetModule = 'ext.gadget.calculator-' + $( this ).attr( 'data-module' );
if( gadgetModule && mw.loader.getState( gadgetModule ) === 'registered' ) {
mw.loader.load( gadgetModule );
}
} );
},
isMobile: function() {
return window.matchMedia( 'only screen and (max-width: 760px)' ).matches;
},
setValue: function( varId, value ) {
if( !mw.calculators.variables.hasOwnProperty( varId ) ) {
return false;
}
if( mw.calculators.variables[ varId ].setValue( value ) ) {
mw.cookie.set( mw.calculators.getCookieKey( varId ), value, {
expires: COOKIE_EXPIRATION
} );
return true;
}
return false;
}
};
/**
* Class CalculatorObject
*
* @param {Object} properties
* @param {Object} propertyValues
* @returns {mw.calculators.objectClasses.CalculatorObject}
* @constructor
*/
mw.calculators.objectClasses.CalculatorObject = function( properties, propertyValues ) {
if( properties ) {
if( properties.hasOwnProperty( 'required' ) ) {
for( var iRequiredProperty in properties.required ) {
var requiredProperty = properties.required[ iRequiredProperty ];
if( !propertyValues || !propertyValues.hasOwnProperty( requiredProperty ) ) {
console.error( 'Missing required property "' + requiredProperty + '"' );
console.log( propertyValues );
return null;
}
this[ requiredProperty ] = propertyValues[ requiredProperty ];
delete propertyValues[ requiredProperty ];
}
}
if( properties.hasOwnProperty( 'optional' ) ) {
for( var iOptionalProperty in properties.optional ) {
var optionalProperty = properties.optional[ iOptionalProperty ];
if( propertyValues && propertyValues.hasOwnProperty( optionalProperty ) ) {
this[ optionalProperty ] = propertyValues[ optionalProperty ];
delete propertyValues[ optionalProperty ];
} else if( typeof this[ optionalProperty ] === 'undefined' ) {
this[ optionalProperty ] = null;
}
}
}
var invalidProperties = Object.keys( propertyValues );
if( invalidProperties.length ) {
console.warn( 'Unsupported properties defined for ' + typeof this + ' with id "' + this.id + '": ' + invalidProperties.join( ', ' ) );
}
}
};
/**
* Class CalculatorUnitsBase
* @param {Object} propertyValues
* @returns {mw.calculators.objectClasses.CalculatorUnitsBase}
* @constructor
*/
mw.calculators.objectClasses.CalculatorUnitsBase = function( propertyValues ) {
var properties = {
required: [
'id'
],
optional: [
'toString'
]
};
mw.calculators.objectClasses.CalculatorObject.call( this, properties, propertyValues );
};
mw.calculators.objectClasses.CalculatorUnitsBase.prototype = Object.create( mw.calculators.objectClasses.CalculatorObject.prototype );
/**
* Class CalculatorUnits
* @param {Object} propertyValues
* @returns {mw.calculators.objectClasses.CalculatorUnits}
* @constructor
*/
mw.calculators.objectClasses.CalculatorUnits = function( propertyValues ) {
var properties = {
required: [
'id'
],
optional: [
'aliases',
'baseName',
'definition',
'offset',
'prefixes'
]
};
mw.calculators.objectClasses.CalculatorObject.call( this, properties, propertyValues );
};
mw.calculators.objectClasses.CalculatorUnits.prototype = Object.create( mw.calculators.objectClasses.CalculatorObject.prototype );
/**
* Class CalculatorVariable
* @param {Object} propertyValues
* @returns {mw.calculators.objectClasses.CalculatorVariable}
* @constructor
*/
mw.calculators.objectClasses.CalculatorVariable = function( propertyValues ) {
var properties = {
required: [
'id',
'name',
'type'
],
optional: [
'abbreviation',
'defaultValue',
'maxLength',
'options',
'units'
]
};
mw.calculators.objectClasses.CalculatorObject.call( this, properties, propertyValues );
if( VALID_TYPES.indexOf( this.type ) === -1 ) {
throw new Error( 'Invalid type "' + this.type + '" for variable "' + this.id + '"' );
}
this.calculations = [];
if( this.defaultValue ) {
this.setValue( this.defaultValue );
} else {
this.value = null;
}
};
mw.calculators.objectClasses.CalculatorVariable.prototype = Object.create( mw.calculators.objectClasses.CalculatorObject.prototype );
mw.calculators.objectClasses.CalculatorVariable.prototype.addCalculation = function( calculationId ) {
if( this.calculations.indexOf( calculationId ) !== -1 ) {
return;
}
this.calculations.push( calculationId );
};
mw.calculators.objectClasses.CalculatorVariable.prototype.createInput = function() {
var varId = this.getId();
var value = this.getValue();
var inputContainerAttribs = {
class: 'form-group calculator-container-input'
};
inputContainerAttribs.class = inputContainerAttribs.class + ' calculator-container-input-' + this.getName();
// Create the input container
var $inputContainer = $( '<div>', inputContainerAttribs );
// Set the input id
var inputId = 'calculator-input-' + varId;
// Initialize label attributes
var labelAttributes = {
for: inputId,
text: this.getLabelString()
};
// Create the input label and append to the container
$inputContainer.append( $( '<label>', labelAttributes ) );
if( this.type === TYPE_NUMBER ) {
// Initialize the primary units variables (needed for handlers, even if doesn't have units)
var unitsId = null;
var $unitsContainer = null;
// Initialize input options
var inputAttributes = {
id: inputId,
class: 'form-control calculator-input-text',
type: 'text',
autocomplete: 'off',
inputmode: 'decimal',
value: this.isValueMathObject() ? value.toNumber() : value
};
// Configure additional options
if( this.maxLength ) {
inputAttributes.maxlength = this.maxLength;
}
// Add the input id to the list of classes
inputAttributes.class = inputAttributes.class + ' ' + inputId;
// If the variable has units, create the units input
if( this.hasUnits() ) {
// Set the units id
unitsId = inputId + '-units';
var unitsValue = this.isValueMathObject() ? value.formatUnits() : null;
// Create the units container
$unitsContainer = $( '<div>', {
class: 'input-group-append'
} );
// Initialize the units input options
var unitsInputAttributes = {
id: unitsId,
class: 'custom-select calculator-input-select'
};
unitsInputAttributes.class = unitsInputAttributes.class + ' ' + unitsId;
var $unitsInput = $( '<select>', unitsInputAttributes )
.on( 'change', function() {
var newValue = $( '#' + inputId ).val() + ' ' + $( this ).val();
mw.calculators.setValue( varId, newValue );
} );
for( var iUnits in this.units ) {
var units = this.units[ iUnits ];
var unitsOptionAttributes = {
text: mw.calculators.getUnitsString( math.unit( '0 ' + units ) ),
value: units
};
if( units === unitsValue ) {
unitsOptionAttributes.selected = true;
}
$unitsInput.append( $( '<option>', unitsOptionAttributes ) );
}
$unitsContainer.append( $unitsInput );
}
// Create the input and add handlers
var $input = $( '<input>', inputAttributes )
.on( 'input', function() {
var newValue = $( this ).val();
if( unitsId ) {
newValue = newValue + ' ' + $( '#' + unitsId ).val();
}
mw.calculators.setValue( varId, newValue );
} );
// Create the input group
var $inputGroup = $( '<div>', {
class: 'input-group'
} ).append( $input );
if( $unitsContainer ) {
$inputGroup.append( $unitsContainer );
}
$inputContainer.append( $inputGroup );
} else if( this.type === TYPE_STRING ) {
if( this.hasOptions() ) {
var varOptions = this.getOptions();
var selectAttributes = {
id: inputId,
class: 'custom-select calculator-input-select'
};
var $select = $( '<select>', selectAttributes )
.on( 'change', function() {
mw.calculators.setValue( varId, $( this ).val() );
} );
for( var iVarOption in varOptions ) {
var varOption = varOptions[ iVarOption ];
var optionAttributes = {
value: varOption,
text: varOption
};
if( varOption === value ) {
optionAttributes.selected = true;
}
$select.append( $( '<option>', optionAttributes ) );
}
$inputContainer.append( $select );
}
}
return $inputContainer;
};
mw.calculators.objectClasses.CalculatorVariable.prototype.getId = function() {
return this.id;
};
mw.calculators.objectClasses.CalculatorVariable.prototype.getLabelString = function() {
return mw.calculators.isMobile() && this.abbreviation ? this.abbreviation : this.name;
};
mw.calculators.objectClasses.CalculatorVariable.prototype.getName = function() {
return this.name;
};
mw.calculators.objectClasses.CalculatorVariable.prototype.getOptions = function() {
return this.options;
};
mw.calculators.objectClasses.CalculatorVariable.prototype.getValue = function() {
return this.value;
};
mw.calculators.objectClasses.CalculatorVariable.prototype.getValueString = function() {
return String( this.value );
};
mw.calculators.objectClasses.CalculatorVariable.prototype.hasOptions = function() {
return this.options !== null;
};
mw.calculators.objectClasses.CalculatorVariable.prototype.hasUnits = function() {
return this.units !== null;
};
mw.calculators.objectClasses.CalculatorVariable.prototype.hasValue = function() {
if( !this.value ||
( this.isValueMathObject() && !this.value.toNumber() ) ) {
return false;
}
return true;
};
mw.calculators.objectClasses.CalculatorVariable.prototype.isValueMathObject = function() {
return this.value && this.value.hasOwnProperty( 'value' );
};
mw.calculators.objectClasses.CalculatorVariable.prototype.setValue = function( value ) {
if( this.type === TYPE_NUMBER ) {
if( typeof value !== 'object' ) {
value = math.unit( value );
}
if( this.hasUnits() ) {
var valueUnits = value.formatUnits();
if( !valueUnits ) {
throw new Error( 'Could not set value for "' + this.getId() + '": Value must define units' );
} else if( this.units.indexOf( valueUnits ) === -1 ) {
throw new Error( 'Could not set value for "' + this.getId() + '": Units "' + valueUnits + '" are not valid for this variable' );
}
}
} else if( this.hasOptions() ) {
if( this.options.indexOf( value ) === -1 ) {
throw new Error( 'Could not set value "' + value + '" for "' + this.getId() + '": Value must define be one of: ' + this.options.join( ', ' ) );
}
}
this.value = value;
for( var iCalculation in this.calculations ) {
var calculation = mw.calculators.getCalculation( this.calculations[ iCalculation ] );
if( calculation ) {
calculation.render();
}
}
return true;
};
/**
* Class CalculatorCalculation
* @param {Object} propertyValues
* @returns {mw.calculators.objectClasses.CalculatorCalculation}
* @constructor
*/
mw.calculators.objectClasses.CalculatorCalculation = function( propertyValues ) {
var properties = {
required: [
'id',
'name',
'calculate'
],
optional: [
'abbreviation',
'data',
'digits',
'formula',
'link',
'references',
'onRender',
'onRendered',
'type',
'units'
]
};
mw.calculators.objectClasses.CalculatorObject.call( this, properties, propertyValues );
if( typeof this.calculate !== 'function' ) {
throw new Error( 'calculate() must be a function for Calculation "' + this.id + '"' );
}
// Initialize array to store calculation ids which depend on this calculation's value
this.calculations = [];
this.type = this.type ? this.type : TYPE_NUMBER;
dataPrototype = {
optional: [],
required: []
};
this.data = this.data ? this.data : {
calculations: dataPrototype,
variables: dataPrototype
};
if( !this.data.hasOwnProperty( 'calculations' ) ) {
this.data.calculations = dataPrototype;
} else {
this.data.calculations.optional = this.data.calculations.hasOwnProperty( 'optional' ) ? this.data.calculations.optional : [];
this.data.calculations.required = this.data.calculations.hasOwnProperty( 'required' ) ? this.data.calculations.required : [];
}
if( !this.data.hasOwnProperty( 'variables' ) ) {
this.data.variables = dataPrototype;
} else {
this.data.variables.optional = this.data.variables.hasOwnProperty( 'optional' ) ? this.data.variables.optional : [];
this.data.variables.required = this.data.variables.hasOwnProperty( 'required' ) ? this.data.variables.required : [];
}
this.message = null;
this.value = null;
};
mw.calculators.objectClasses.CalculatorCalculation.prototype = Object.create( mw.calculators.objectClasses.CalculatorObject.prototype );
mw.calculators.objectClasses.CalculatorCalculation.prototype.addCalculation = function( calculationId ) {
if( this.calculations.indexOf( calculationId ) !== -1 ) {
return;
}
this.calculations.push( calculationId );
};
mw.calculators.objectClasses.CalculatorCalculation.prototype.getId = function() {
return this.id;
};
mw.calculators.objectClasses.CalculatorCalculation.prototype.getContainerClass = function() {
return 'calculator-calculation-' + this.id;
};
mw.calculators.objectClasses.CalculatorCalculation.prototype.getLabelHtml = function() {
var labelHtml = this.getLabelString();
if( this.link ) {
var href = this.link;
// Detect internal links (this isn't great)
var matches = href.match(/\[\[(.*?)\]\]/);
if( matches ) {
href = mw.util.getUrl( matches[ 1 ] );
}
labelHtml = $( '<a>', {
href: href,
text: labelHtml
} )[ 0 ].outerHTML;
}
return labelHtml;
};
mw.calculators.objectClasses.CalculatorCalculation.prototype.getLabelString = function() {
return mw.calculators.isMobile() && this.abbreviation ? this.abbreviation : this.name;
};
mw.calculators.objectClasses.CalculatorCalculation.prototype.getValueString = function() {
if( this.message ) {
return this.message;
} else if( typeof this.value === 'object' && this.value.hasOwnProperty( 'value' ) ) {
var value, units;
if( this.units ) {
value = this.value.toNumber( this.units );
units = mw.calculators.getUnitsString( this.value );
} else {
value = this.value.toNumber();
}
var digits = this.digits !== null ? this.digits : 1;
value = value.toFixed( digits );
if( units ) {
value = value + ' ' + this.value.formatUnits().replace( /\s/g, '' );
}
return value;
} else {
return String( this.value );
}
};
mw.calculators.objectClasses.CalculatorCalculation.prototype.hasInfo = function() {
return this.formula || this.references.length;
};
mw.calculators.objectClasses.CalculatorCalculation.prototype.hasValue = function() {
if( !this.value ||
( this.isValueMathObject() && !this.value.toNumber() ) ) {
return false;
}
return true;
};
mw.calculators.objectClasses.CalculatorCalculation.prototype.isValueMathObject = function() {
return this.value && this.value.hasOwnProperty( 'value' );
};
mw.calculators.objectClasses.CalculatorCalculation.prototype.recalculate = function() {
this.message = '';
var data = {};
var missingRequiredData = '';
var calculationId, calculation, varId, variable;
for( var iRequiredCalculation in this.data.calculations.required ) {
calculationId = this.data.calculations.required[ iRequiredCalculation ];
calculation = mw.calculators.getCalculation( calculationId );
if( !calculation ) {
throw new Error( 'Invalid required calculation "' + calculationId + '" for calculation "' + this.id + '"' );
} else if( !calculation.hasValue() ) {
if( missingRequiredData ) {
missingRequiredData = missingRequiredData + ', ';
}
missingRequiredData = missingRequiredData + calculation.getLabelString();
} else {
data[ calculationId ] = calculation.value;
}
}
for( var iRequiredVariable in this.data.variables.required ) {
varId = this.data.variables.required[ iRequiredVariable ];
variable = mw.calculators.getVariable( varId );
if( !variable ) {
throw new Error( 'Invalid required variable "' + varId + '" for calculation "' + this.id + '"' );
} else if( !variable.hasValue() ) {
if( missingRequiredData ) {
missingRequiredData = missingRequiredData + ', ';
}
missingRequiredData = missingRequiredData + variable.getLabelString();
} else {
data[ varId ] = variable.getValue();
}
}
if( missingRequiredData ) {
this.message = missingRequiredData + ' required';
return false;
}
for( var iOptionalVariable in this.data.variables.optional ) {
varId = this.data.variables.optional[ iOptionalVariable ];
variable = mw.calculators.getVariable( varId );
if( !variable ) {
throw new Error( 'Invalid optional variable "' + varId + '" for calculation "' + this.id + '"' );
}
data[ varId ] = variable.hasValue() ? variable.getValue() : null;
}
for( var iOptionalCalculation in this.data.calculations.optional ) {
calculationId = this.data.calculations.optional[ calculationId ];
calculation = mw.calculators.getVariable( calculationId );
if( !calculation ) {
throw new Error( 'Invalid optional variable "' + calculationId + '" for calculation "' + this.id + '"' );
}
data[ calculationId ] = calculation.hasValue() ? calculation.getValue() : null;
}
try {
var value = this.calculate( data );
if( this.type === TYPE_NUMBER && !isNaN( value ) ) {
if( this.units ) {
value = value + ' ' + this.units;
}
this.value = math.unit( value );
} else {
this.value = value;
}
for( var iCalculation in this.calculations ) {
var calculation = mw.calculators.getCalculation( this.calculations[ iCalculation ] );
if( calculation ) {
calculation.render();
}
}
} catch( e ) {
this.message = e.message;
this.value = null;
}
return true;
};
mw.calculators.objectClasses.CalculatorCalculation.prototype.render = function() {
var $calculationContainer = $( '.' + this.getContainerClass() );
if( !$calculationContainer.length ) {
return;
}
this.recalculate();
if( typeof this.onRender === 'function' ) {
this.onRender( $calculationContainer );
}
var valueString = this.getValueString();
var inputVariableIds = this.data.variables.required.concat( this.data.variables.optional );
var missingVariableInputs = [];
for( var iInputVariableId in inputVariableIds ) {
var variableId = inputVariableIds[ iInputVariableId ];
if( !$( '#calculator-input-' + variableId ).length ) {
missingVariableInputs.push( variableId );
}
}
var calculation = this;
$calculationContainer.each( function() {
$( this ).empty();
var isTable = this.tagName.toLowerCase() === 'tr';
var $infoButton = null;
if( calculation.hasInfo() ) {
$infoButton = $( '<a>', {
'data-toggle': 'collapse',
href: '#collapseInfo',
role: 'button',
'aria-expanded': 'false',
'aria-controls': calculation.getContainerClass() + '-info'
} )
.append( $( '<i>', {
class: 'far fa-question-circle'
} ) );
}
if( isTable ) {
$( this )
.append( $( '<th>', {
html: calculation.getLabelHtml()
} ) )
.append( $( '<td>', {
colspan: calculation.hasInfo() ? 1 : 2,
html: valueString
} ) );
if( calculation.hasInfo() ) {
$( this )
.append( $( '<td>', {
class: 'calculator-calculation-column-info'
} ).append( $infoButton ) );
}
} else {
$( this )
.append( calculation.getLabelHtml() + $infoButton + ': ' + valueString );
}
if( missingVariableInputs.length ) {
var variablesContainerClass = 'calculator-calculation-variables ' + calculation.getContainerClass() + '-variables';
var inputGroup = mw.calculators.createInputGroup( missingVariableInputs );
if( isTable ) {
$variablesContainer = $( '<tr>' )
.append( $( '<td>', {
class: variablesContainerClass,
colspan: calculation.hasInfo() ? 3 : 2
} ).append( inputGroup ) );
} else {
$variablesContainer = $( '<div>', {
class: variablesContainerClass
} ).append( inputGroup );
}
$( this ).after( $variablesContainer );
missingVariableInputs = [];
}
// tr class=variables
// tr class=info
} );
return;
if( missingVariableInputs.length ) {
var $variableInputs = $( '<div>', {
class: 'calculator-calculation-variables ' + this.getContainerClass() + '-variables'
} );
$variableInputs.append( mw.calculators.createInputGroup( missingVariableInputs ) );
$calculationContainer.after( $variableInputs );
}
this.renderLabel();
this.renderInfo();
if( typeof this.onRendered === 'function' ) {
this.onRendered( $calculationContainer );
}
};
mw.calculators.objectClasses.CalculatorCalculation.prototype.renderInfo = function() {
var $infoContainer = $( '.' + this.getContainerClass() + '-info' );
if( !$infoContainer.length ) {
return;
}
var infoHtml = '';
if( this.references.length ) {
var $references = $( '<ol>' );
for( var iReference in this.references ) {
$references.append( $( '<li>', {
text: this.references[ iReference ]
} ) );
}
infoHtml = infoHtml + $references[ 0 ].outerHTML;
}
var $info = $( '<a> ', {
'data-toggle': 'tooltip',
'data-html': true,
title: infoHtml
} );
$info.append( $( '<i>', {
class: 'far fa-question-circle'
} ) );
$infoContainer.empty().append( $info );
};
/**
* Class CalculatorCalculator
* @param {Object} propertyValues
* @returns {mw.calculators.objectClasses.CalculatorCalculator}
* @constructor
*/
mw.calculators.objectClasses.CalculatorCalculator = function( propertyValues ) {
var properties = {
required: [
'id',
'name',
'calculations'
],
optional: [
'css',
'onRender',
'onRendered',
'table'
]
};
mw.calculators.objectClasses.CalculatorObject.call( this, properties, propertyValues );
};
mw.calculators.objectClasses.CalculatorCalculator.prototype = Object.create( mw.calculators.objectClasses.CalculatorObject.prototype );
mw.calculators.objectClasses.CalculatorCalculator.prototype.getContainerClass = function() {
return 'calculator-' + this.id;
};
mw.calculators.objectClasses.CalculatorCalculator.prototype.render = function() {
var $calculatorContainer = $( '.' + this.getContainerClass() );
if( !$calculatorContainer.length ) {
return;
}
if( typeof this.onRender === 'function' ) {
this.onRender( $calculatorContainer );
}
if( this.css ) {
$calculatorContainer.css( this.css );
}
$calculatorContainer.empty();
$calculatorContainer.append( $( '<h4>', {
text: this.name
} ) );
var $calculationsContainer;
if( this.table ) {
$calculationsContainer = $( '<table>', {
class: 'wikitable'
} ).append( '<tbody>' );
} else {
$calculationsContainer = $( '<div>' );
}
$calculatorContainer.append( $calculationsContainer );
for( var iCalculationId in this.calculations ) {
var calculation = mw.calculators.getCalculation( this.calculations[ iCalculationId ] );
var calculationContainerClass = calculation.getContainerClass();
var $calculationContainer = $( '.' + calculationContainerClass );
// If a container doesn't exist yet, add it
if( !$calculationContainer.length ) {
if( this.table ) {
$calculationContainer = $( '<tr>', {
class: calculationContainerClass
} );
} else {
$calculationContainer = $( '<div>', {
class: calculationContainerClass
} );
}
$calculationsContainer.append( $calculationContainer );
}
calculation.render();
}
$( '[data-toggle="tooltip"]' ).tooltip();
if( typeof this.onRendered === 'function' ) {
this.onRendered( $calculatorContainer );
}
};
mw.calculators.init();
}() );