Difference between revisions of "MediaWiki:Gadget-calculator-core.js"
From WikiAnesthesia
Chris Rishel (talk | contribs) |
Chris Rishel (talk | contribs) |
||
Line 36: | Line 36: | ||
TYPE_STRING | TYPE_STRING | ||
]; | ]; | ||
mw.calculators = { | |||
calculations: {}, | |||
objectClasses: {}, | |||
variables: {}, | |||
addCalculations: function( calculations ) { | |||
for( var calcId in calculations ) { | |||
if( mw.calculators.calculations.hasOwnProperty( calcId ) ) { | |||
console.warn( 'Calculation variable "' + calcId + '" already defined.' ); | |||
continue; | |||
} | |||
var calcData = calculations[ calcId ]; | |||
calcData.id = calcId; | |||
var calculation = new mw.calculators.objectClasses.CalculatorCalculation( calcData ); | |||
if( !calculation ) { | |||
continue; | |||
} | |||
mw.calculators.calculations[ calcId ] = calculation; | |||
var variables = calculation.variables.required.concat( calculation.variables.optional ); | |||
for( var iVariable in variables ) { | |||
var varId = variables[ iVariable ]; | |||
if( !mw.calculators.variables.hasOwnProperty( varId ) ) { | |||
throw new Error('Variable "' + varId + '" does not exist for calculation "' + calcId + '"'); | |||
} | |||
mw.calculators.variables[ varId ].addCalculation( calcId ); | |||
} | |||
} | |||
}, | |||
addVariables: function( variables ) { | |||
for( var varId in variables ) { | |||
if( mw.calculators.variables.hasOwnProperty( varId ) ) { | |||
console.warn( 'Calculation variable "' + varId + '" already defined.' ); | |||
continue; | |||
} | |||
var varData = variables[ varId ]; | |||
varData.id = varId; | |||
var variable = new mw.calculators.objectClasses.CalculatorVariable( varData ); | |||
if( variable ) { | |||
var cookieValue = mw.calculators.getCookieValue( varId ); | |||
if( cookieValue ) { | |||
variable.setValue( cookieValue ); | |||
} | |||
mw.calculators.variables[ varId ] = variable; | |||
} | |||
} | |||
}, | |||
defineUnits: function() { | |||
// Create aliases for abbreviations of existing units | |||
math.createUnit( 'dy', { | |||
definition: '1 day' | |||
} ); | |||
math.createUnit( 'mo', { | |||
definition: '1 month' | |||
} ); | |||
math.createUnit( 'yr', { | |||
definition: '1 year' | |||
} ); | |||
// Body weight needs to be treated as a different fundamental unit type compared to mass for stoichiometry | |||
// Gram-weight, which uses short SI prefixes (e.g. 1 kgwt = 1000 gwt) | |||
math.createUnit( 'gwt', { | |||
prefixes: 'short' | |||
} ); | |||
// Pound-weight, which uses no prefixes | |||
math.createUnit( 'lbwt', { | |||
definition: '453.59237 gwt' | |||
} ); | |||
math.createUnit( 'puff' ); | |||
math.createUnit( 'unit', { | |||
aliases: [ 'units' ] | |||
} ); | |||
math.createUnit( 'vial', { | |||
aliases: [ 'vials' ] | |||
} ); | |||
}, | |||
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( calcId ) { | |||
if( mw.calculators.calculations.hasOwnProperty( calcId ) ) { | |||
return mw.calculators.calculations[ calcId ]; | |||
} else { | |||
return null; | |||
} | |||
}, | |||
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() { | |||
mw.calculators.defineUnits(); | |||
}, | |||
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; | |||
} | |||
}; | |||
/** | /** | ||
Line 45: | Line 195: | ||
* @constructor | * @constructor | ||
*/ | */ | ||
function | mw.calculators.objectClasses.CalculatorObject = function( properties, propertyValues ) { | ||
if( properties ) { | if( properties ) { | ||
if( properties.hasOwnProperty( 'required' ) ) { | if( properties.hasOwnProperty( 'required' ) ) { | ||
Line 74: | Line 224: | ||
} | } | ||
} | } | ||
} | }; | ||
mw.calculators.objectClasses.CalculatorObject.prototype.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; | |||
}; | |||
Line 83: | Line 253: | ||
* @constructor | * @constructor | ||
*/ | */ | ||
function | mw.calculators.objectClasses.CalculatorVariable = function( propertyValues ) { | ||
var properties = { | var properties = { | ||
required: [ | required: [ | ||
Line 99: | Line 269: | ||
] | ] | ||
}; | }; | ||
mw.calculators.objectClasses.CalculatorObject.call( this, properties, propertyValues ); | |||
if( VALID_TYPES.indexOf( this.type ) === -1 ) { | if( VALID_TYPES.indexOf( this.type ) === -1 ) { | ||
throw new Error( 'Invalid type "' + this.type + '" for variable "' + this.id + '"' ); | throw new Error( 'Invalid type "' + this.type + '" for variable "' + this.id + '"' ); | ||
Line 115: | Line 285: | ||
} | } | ||
CalculatorVariable.prototype = Object.create( CalculatorObject.prototype ); | mw.calculators.objectClasses.CalculatorVariable.prototype = Object.create( mw.calculators.objectClasses.CalculatorObject.prototype ); | ||
CalculatorVariable.prototype.addCalculation = function( calcId ) { | mw.calculators.objectClasses.CalculatorVariable.prototype.addCalculation = function( calcId ) { | ||
if( this.calculations.indexOf( calcId ) !== -1 ) { | if( this.calculations.indexOf( calcId ) !== -1 ) { | ||
return; | return; | ||
Line 125: | Line 295: | ||
}; | }; | ||
CalculatorVariable.prototype.createInput = function( options ) { | mw.calculators.objectClasses.CalculatorVariable.prototype.createInput = function( options ) { | ||
// Initialize options | // Initialize options | ||
options = ( typeof options !== 'undefined' ) ? options : {}; | options = ( typeof options !== 'undefined' ) ? options : {}; | ||
Line 321: | Line 491: | ||
}; | }; | ||
CalculatorVariable.prototype.getId = function() { | mw.calculators.objectClasses.CalculatorVariable.prototype.getId = function() { | ||
return this.id; | return this.id; | ||
}; | }; | ||
CalculatorVariable.prototype.getName = function() { | mw.calculators.objectClasses.CalculatorVariable.prototype.getName = function() { | ||
return this.name; | return this.name; | ||
}; | }; | ||
CalculatorVariable.prototype.getOptions = function() { | mw.calculators.objectClasses.CalculatorVariable.prototype.getOptions = function() { | ||
return this.options; | return this.options; | ||
}; | }; | ||
CalculatorVariable.prototype.getType = function() { | mw.calculators.objectClasses.CalculatorVariable.prototype.getType = function() { | ||
return this.type; | return this.type; | ||
}; | }; | ||
CalculatorVariable.prototype.getValue = function() { | mw.calculators.objectClasses.CalculatorVariable.prototype.getValue = function() { | ||
return this.value; | return this.value; | ||
}; | }; | ||
CalculatorVariable.prototype.getValueString = function() { | mw.calculators.objectClasses.CalculatorVariable.prototype.getValueString = function() { | ||
return String( this.value ); | return String( this.value ); | ||
}; | }; | ||
CalculatorVariable.prototype.hasOptions = function() { | mw.calculators.objectClasses.CalculatorVariable.prototype.hasOptions = function() { | ||
return this.options !== null; | return this.options !== null; | ||
}; | }; | ||
CalculatorVariable.prototype.hasUnits = function() { | mw.calculators.objectClasses.CalculatorVariable.prototype.hasUnits = function() { | ||
return this.units !== null; | return this.units !== null; | ||
}; | }; | ||
CalculatorVariable.prototype.hasValue = function() { | mw.calculators.objectClasses.CalculatorVariable.prototype.hasValue = function() { | ||
if( !this.value || | if( !this.value || | ||
( this.isValueMathObject() && !this.value.toNumber() ) ) { | ( this.isValueMathObject() && !this.value.toNumber() ) ) { | ||
Line 362: | Line 532: | ||
}; | }; | ||
CalculatorVariable.prototype.isValueMathObject = function() { | mw.calculators.objectClasses.CalculatorVariable.prototype.isValueMathObject = function() { | ||
return this.value && this.value.hasOwnProperty( 'value' ); | return this.value && this.value.hasOwnProperty( 'value' ); | ||
}; | }; | ||
CalculatorVariable.prototype.setValue = function( value ) { | mw.calculators.objectClasses.CalculatorVariable.prototype.setValue = function( value ) { | ||
if( this.getType() === 'number' ) { | if( this.getType() === 'number' ) { | ||
if( typeof value !== 'object' ) { | if( typeof value !== 'object' ) { | ||
Line 399: | Line 569: | ||
return true; | return true; | ||
}; | }; | ||
/** | /** | ||
Line 407: | Line 576: | ||
* @constructor | * @constructor | ||
*/ | */ | ||
function | mw.calculators.objectClasses.CalculatorCalculation = function( propertyValues ) { | ||
var properties = { | var properties = { | ||
required: [ | required: [ | ||
Line 425: | Line 594: | ||
}; | }; | ||
CalculatorObject.call( this, properties, propertyValues ); | mw.calculators.objectClasses.CalculatorObject.call( this, properties, propertyValues ); | ||
if( typeof this.calculate !== 'function' ) { | if( typeof this.calculate !== 'function' ) { | ||
Line 445: | Line 614: | ||
} | } | ||
CalculatorCalculation.prototype = Object.create( CalculatorObject.prototype ); | mw.calculators.objectClasses.CalculatorCalculation.prototype = Object.create( mw.calculators.objectClasses.CalculatorObject.prototype ); | ||
CalculatorCalculation.prototype.getId = function() { | mw.calculators.objectClasses.CalculatorCalculation.prototype.getId = function() { | ||
return this.id; | return this.id; | ||
}; | }; | ||
CalculatorCalculation.prototype.recalculate = function() { | mw.calculators.objectClasses.CalculatorCalculation.prototype.recalculate = function() { | ||
this.message = ''; | this.message = ''; | ||
Line 508: | Line 677: | ||
}; | }; | ||
CalculatorCalculation.prototype.render = function( options ) { | mw.calculators.objectClasses.CalculatorCalculation.prototype.render = function( options ) { | ||
this.recalculate(); | this.recalculate(); | ||
Line 538: | Line 707: | ||
$renderOutput = $( '<div>', { | $renderOutput = $( '<div>', { | ||
class: containerClass | class: containerClass | ||
} ).text( labelString + ': ' + valueString ); | |||
} | } | ||
Line 544: | Line 713: | ||
}; | }; | ||
CalculatorCalculation.prototype.renderLabel = function( options ) { | mw.calculators.objectClasses.CalculatorCalculation.prototype.renderLabel = function( options ) { | ||
return options && options.hasOwnProperty( 'label' ) && | return options && options.hasOwnProperty( 'label' ) && | ||
options.label === RENDERLABEL_ABBREVIATION && | |||
this.abbreviation ? this.abbreviation : this.name; | |||
}; | }; | ||
CalculatorCalculation.prototype.toString = function() { | mw.calculators.objectClasses.CalculatorCalculation.prototype.toString = function() { | ||
if( this.message ) { | if( this.message ) { | ||
return this.message; | return this.message; | ||
Line 568: | Line 737: | ||
return String( this.value ); | return String( this.value ); | ||
} | } | ||
}; | }; | ||
Revision as of 23:01, 20 July 2021
/** * @author Chris Rishel */ ( function() { const COOKIE_EXPIRATION = 12 * 60 * 60; const RENDERFORMAT_PLAIN = 'plain'; const RENDERFORMAT_ROW = 'row'; const VALID_RENDERFORMATS = [ RENDERFORMAT_PLAIN, RENDERFORMAT_ROW ]; const RENDERLABEL_NAME = 'name'; const RENDERLABEL_ABBREVIATION = 'abbreviation'; const VALID_RENDERLABELS = [ RENDERLABEL_NAME, RENDERLABEL_ABBREVIATION ]; const INPUTSIZE_COMPACT = 'compact'; const INPUTSIZE_FULL = 'full'; const VALID_INPUTSIZES = [ INPUTSIZE_COMPACT, INPUTSIZE_FULL ]; const TYPE_NUMBER = 'number'; const TYPE_STRING = 'string'; const VALID_TYPES = [ TYPE_NUMBER, TYPE_STRING ]; mw.calculators = { calculations: {}, objectClasses: {}, variables: {}, addCalculations: function( calculations ) { for( var calcId in calculations ) { if( mw.calculators.calculations.hasOwnProperty( calcId ) ) { console.warn( 'Calculation variable "' + calcId + '" already defined.' ); continue; } var calcData = calculations[ calcId ]; calcData.id = calcId; var calculation = new mw.calculators.objectClasses.CalculatorCalculation( calcData ); if( !calculation ) { continue; } mw.calculators.calculations[ calcId ] = calculation; var variables = calculation.variables.required.concat( calculation.variables.optional ); for( var iVariable in variables ) { var varId = variables[ iVariable ]; if( !mw.calculators.variables.hasOwnProperty( varId ) ) { throw new Error('Variable "' + varId + '" does not exist for calculation "' + calcId + '"'); } mw.calculators.variables[ varId ].addCalculation( calcId ); } } }, addVariables: function( variables ) { for( var varId in variables ) { if( mw.calculators.variables.hasOwnProperty( varId ) ) { console.warn( 'Calculation variable "' + varId + '" already defined.' ); continue; } var varData = variables[ varId ]; varData.id = varId; var variable = new mw.calculators.objectClasses.CalculatorVariable( varData ); if( variable ) { var cookieValue = mw.calculators.getCookieValue( varId ); if( cookieValue ) { variable.setValue( cookieValue ); } mw.calculators.variables[ varId ] = variable; } } }, defineUnits: function() { // Create aliases for abbreviations of existing units math.createUnit( 'dy', { definition: '1 day' } ); math.createUnit( 'mo', { definition: '1 month' } ); math.createUnit( 'yr', { definition: '1 year' } ); // Body weight needs to be treated as a different fundamental unit type compared to mass for stoichiometry // Gram-weight, which uses short SI prefixes (e.g. 1 kgwt = 1000 gwt) math.createUnit( 'gwt', { prefixes: 'short' } ); // Pound-weight, which uses no prefixes math.createUnit( 'lbwt', { definition: '453.59237 gwt' } ); math.createUnit( 'puff' ); math.createUnit( 'unit', { aliases: [ 'units' ] } ); math.createUnit( 'vial', { aliases: [ 'vials' ] } ); }, 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( calcId ) { if( mw.calculators.calculations.hasOwnProperty( calcId ) ) { return mw.calculators.calculations[ calcId ]; } else { return null; } }, 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() { mw.calculators.defineUnits(); }, 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} varData * @param {Object} propertyData * @returns {Object} * @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.hasOwnProperty( requiredProperty ) ) { console.error( 'Missing required property "' + requiredProperty + '"' ); console.log( propertyValues ); return null; } this[ requiredProperty ] = propertyValues[ requiredProperty ]; } } if( properties.hasOwnProperty( 'optional' ) ) { for( var iOptionalProperty in properties.optional ) { var optionalProperty = properties.optional[ iOptionalProperty ]; if( propertyValues.hasOwnProperty( optionalProperty ) ) { this[ optionalProperty ] = propertyValues[ optionalProperty ]; } else { this[ optionalProperty ] = null; } } } } }; mw.calculators.objectClasses.CalculatorObject.prototype.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; }; /** * Class CalculatorVariable * @param {Object} varData * @returns {CalculatorVariable} * @constructor */ mw.calculators.objectClasses.CalculatorVariable = function( propertyValues ) { var properties = { required: [ 'id', 'name', 'type' ], optional: [ 'abbreviation', 'defaultValue', 'maxLength', 'options', 'renderUnits', '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( calcId ) { if( this.calculations.indexOf( calcId ) !== -1 ) { return; } this.calculations.push( calcId ); }; mw.calculators.objectClasses.CalculatorVariable.prototype.createInput = function( options ) { // Initialize options options = ( typeof options !== 'undefined' ) ? options : {}; if( !options.hasOwnProperty( 'size' ) ) { options.size = 'full'; } else if( VALID_INPUTSIZES.indexOf( options.size ) === -1 ) { throw new Error( 'Invalid input size "' + options.size + '" for variable "' + this.getId() + '"' ); } var varId = this.getId(); var value = this.getValue(); var inputContainerAttribs = { class: 'col-auto calculator-container-input' }; if( options.size === INPUTSIZE_COMPACT ) { inputContainerAttribs.class = inputContainerAttribs.class + ' calculator-container-input-compact'; } 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.getName() }; if( options.size === INPUTSIZE_COMPACT ) { // Only use label for screen readers labelAttributes.class = 'sr-only'; } // Create the input label and append to the container $inputContainer.append( $( '<label>', labelAttributes ) ); if( this.getType() === 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', inputmode: 'decimal', placeholder: this.getName(), value: this.isValueMathObject() ? value.toNumber() : value }; // Configure additional options if( this.maxLength ) { inputAttributes.maxlength = this.maxLength; } if( options.size === INPUTSIZE_COMPACT ) { inputAttributes.class = inputAttributes.class + ' calculator-input-text-compact'; // Switch the placeholder to the abbreviation if defined if( this.abbreviation ) { inputAttributes.placeholder = this.abbreviation; } // Set the size of the input to the maxlength if defined if( inputAttributes.hasOwnProperty( 'maxlength' ) ) { inputAttributes.size = inputAttributes.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' }; if( options.size === INPUTSIZE_COMPACT ) { unitsInputAttributes.class = unitsInputAttributes.class + ' calculator-input-select-compact'; } 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: units, value: units }; // Apply custom rendering function for units if defined if( typeof this.renderUnits === 'function' ) { unitsOptionAttributes.text = this.renderUnits( 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.getType() === TYPE_STRING ) { if( this.hasOptions() ) { var varOptions = this.getOptions(); var selectAttributes = { id: inputId, class: 'custom-select calculator-input-select' }; if( options.size === INPUTSIZE_COMPACT ) { selectAttributes.class = selectAttributes.class + ' calculator-input-select-compact'; } 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.getName = function() { return this.name; }; mw.calculators.objectClasses.CalculatorVariable.prototype.getOptions = function() { return this.options; }; mw.calculators.objectClasses.CalculatorVariable.prototype.getType = function() { return this.type; }; 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.getType() === '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} varData * @returns {CalculatorCalculation} * @constructor */ mw.calculators.objectClasses.CalculatorCalculation = function( propertyValues ) { var properties = { required: [ 'id', 'name', 'calculate' ], optional: [ 'abbreviation', 'digits', 'references', 'render', 'renderLabel', 'units', 'variables' ] }; 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 + '"' ); } this.variables = this.variables ? this.variables : {}; if( !this.variables.hasOwnProperty( 'required' ) ) { this.variables.required = []; } if( !this.variables.hasOwnProperty( 'optional' ) ) { this.variables.optional = []; } this.message = null; this.value = null; } mw.calculators.objectClasses.CalculatorCalculation.prototype = Object.create( mw.calculators.objectClasses.CalculatorObject.prototype ); mw.calculators.objectClasses.CalculatorCalculation.prototype.getId = function() { return this.id; }; mw.calculators.objectClasses.CalculatorCalculation.prototype.recalculate = function() { this.message = ''; var data = {}; var missingRequiredData = ''; var varId, variable; for( var iRequiredVariable in this.variables.required ) { varId = this.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 + varId; } else { data[ varId ] = variable.getValue(); } } if( missingRequiredData ) { this.message = missingRequiredData + ' required'; return false; } for( var iOptionalVariable in this.variables.optional ) { varId = this.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; } try { var value = this.calculate( data ); if( this.units ) { value = value + ' ' + this.units; } this.value = math.unit( value ); } catch( e ) { this.message = e.message; this.value = null; } return true; }; mw.calculators.objectClasses.CalculatorCalculation.prototype.render = function( options ) { this.recalculate(); var labelString = this.renderLabel( options ); var valueString = this.toString(); var containerClass = 'calculator-result-' + this.getId(); var $container = $( '.' + containerClass ); if( !$container.length ) { return; } var $renderOutput = null; if( options && options.hasOwnProperty( 'format' ) && options.format === RENDERFORMAT_ROW ) { $renderOutput = $( '<div>', { class: 'row ' + containerClass } ); $renderOutput.append( $( '<div>', { class: 'col' } ) ).append( labelString ); $renderOutput.append( $( '<div>', { class: 'col' } ) ).append( valueString ); } else { $renderOutput = $( '<div>', { class: containerClass } ).text( labelString + ': ' + valueString ); } $container.replaceWith( $renderOutput ); }; mw.calculators.objectClasses.CalculatorCalculation.prototype.renderLabel = function( options ) { return options && options.hasOwnProperty( 'label' ) && options.label === RENDERLABEL_ABBREVIATION && this.abbreviation ? this.abbreviation : this.name; }; mw.calculators.objectClasses.CalculatorCalculation.prototype.toString = function() { if( this.message ) { return this.message; } else if( typeof this.value === 'object' && this.value.hasOwnProperty( 'value' ) ) { var value = this.value.toNumber( this.units ); if( this.digits !== null ) { value = value.toFixed( this.digits ); } if( this.units ) { value = value + ' ' + this.value.formatUnits().replace( /\s/g, '' ); } return value; } else { return String( this.value ); } }; mw.calculators.init(); }() );