Difference between revisions of "MediaWiki:Gadget-calculator-core.js"
From WikiAnesthesia
Chris Rishel (talk | contribs) |
Chris Rishel (talk | contribs) |
||
| Line 3: | Line 3: | ||
*/ | */ | ||
( function() { | ( function() { | ||
const INPUTSIZE_FULL = 'full'; | |||
const INPUTSIZE_COMPACT = 'compact'; | |||
const VALID_INPUTSIZES = [ | |||
INPUTSIZE_FULL, | |||
INPUTSIZE_COMPACT | |||
]; | |||
const TYPE_NUMBER = 'number'; | |||
const TYPE_STRING = 'string'; | |||
const VALID_TYPES = [ | |||
TYPE_NUMBER, | |||
TYPE_STRING | |||
]; | |||
/** | /** | ||
* Class CalculatorObject | * Class CalculatorObject | ||
| Line 52: | Line 68: | ||
var propertyData = { | var propertyData = { | ||
required: [ | required: [ | ||
'id', | |||
'name', | 'name', | ||
'type' | 'type' | ||
], | ], | ||
optional: [ | optional: [ | ||
'abbreviation', | |||
'defaultValue', | 'defaultValue', | ||
'maxLength', | |||
'options', | 'options', | ||
'units' | 'units' | ||
| Line 63: | Line 82: | ||
CalculatorObject.call( this, varData, propertyData ); | CalculatorObject.call( this, varData, propertyData ); | ||
if( VALID_TYPES.indexOf( this.type ) === -1 ) { | |||
throw new Error( 'Invalid type "' + this.type + '" for variable "' + this.id + '"' ); | |||
} | |||
this.value = null; | this.value = null; | ||
| Line 68: | Line 91: | ||
CalculatorVariable.prototype = Object.create( CalculatorObject.prototype ); | CalculatorVariable.prototype = Object.create( CalculatorObject.prototype ); | ||
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 value = this.getValue(); | |||
// Create the input container | |||
var $inputContainer = $( 'div', { | |||
class: 'col-auto calculator-container-input calculator-container-input-' + this.getName() | |||
} ); | |||
if( this.getType() === TYPE_NUMBER ) { | |||
// Set the input id | |||
var inputId = 'calculator-input-' + this.getId(); | |||
// Initialize label attributes | |||
var labelAttributes = { | |||
for: inputId, | |||
text: this.getName() | |||
}; | |||
// Initialize input options | |||
var inputAttributes = { | |||
id: inputId, | |||
class: 'form-control calculator-input-text', | |||
type: 'text', | |||
inputmode: 'decimal', | |||
placeholder: this.getName(), | |||
value: typeof value === 'object' ? value.toNumber() : value | |||
}; | |||
// Configure additional options | |||
if( this.hasOwnProperty( 'maxLength' ) ) { | |||
inputAttributes.maxlength = this.maxLength; | |||
} | |||
if( options.size === INPUTSIZE_COMPACT ) { | |||
// Only use label for screen readers | |||
labelAttributes.class = 'sr-only'; | |||
inputAttributes.class = inputAttributes.class + ' calculator-input-text-compact'; | |||
// Switch the placeholder to the abbreviation if defined | |||
if( this.hasOwnProperty( 'abbreviation' ) ) { | |||
inputAttributes.placeholder = this.abbreviation; | |||
} | |||
// Set the size of the input to the maxlength if defined | |||
if( inputAttributes.hasOwnProperty( 'maxlength' ) ) { | |||
inputAttributes.size = this.maxLength; | |||
} | |||
} | |||
// Add the input id to the list of classes | |||
inputAttributes.class = inputAttributes.class + ' ' + inputId; | |||
// Create the input and add handlers | |||
var $input = $( 'input', inputAttributes ) | |||
.on( 'change', function() { | |||
console.log( $( this ).val() ); | |||
} ); | |||
// Create the input label and append to the container | |||
$inputContainer.append( $( 'label', labelAttributes ) ); | |||
// Create the input group | |||
var $inputGroup = $( 'div', { | |||
class: 'input-group' | |||
} ).append( $input ); | |||
// If the variable has units, create the units input | |||
if( this.hasUnits() ) { | |||
var unitsId = inputId + '-units'; | |||
var unitsValue = typeof value === 'object' ? value.formatUnits() : null; | |||
// Create the units container | |||
var $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 ); | |||
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( this.hasOwnProperty( 'renderUnits' ) ) { | |||
unitsOptionAttributes.text = this.renderUnits( units ); | |||
} | |||
if( units === unitsValue ) { | |||
unitsOptionAttributes.selected = true; | |||
} | |||
$unitsInput.append( $( 'option', unitsOptionAttributes ) ); | |||
} | |||
$unitsContainer.append( $unitsInput ); | |||
$inputGroup.append( $unitsContainer ); | |||
} | |||
$inputContainer.append( $inputGroup ); | |||
} else if( this.getType === TYPE_STRING ) { | |||
} | |||
return $inputContainer; | |||
}; | |||
CalculatorVariable.prototype.getId = function() { | |||
return this.id; | |||
}; | |||
CalculatorVariable.prototype.getName = function() { | |||
return this.name; | |||
}; | |||
CalculatorVariable.prototype.getType = function() { | |||
return this.type; | |||
}; | |||
CalculatorVariable.prototype.getValue = function() { | CalculatorVariable.prototype.getValue = function() { | ||
| Line 99: | Line 266: | ||
this.value = value; | this.value = value; | ||
return true; | |||
}; | }; | ||
| Line 113: | Line 282: | ||
var varData = variables[ varName ]; | var varData = variables[ varName ]; | ||
varData. | varData.id = varName; | ||
var calculationVariable = new CalculatorVariable( varData ); | var calculationVariable = new CalculatorVariable( varData ); | ||
Revision as of 18:07, 18 July 2021
/**
* @author Chris Rishel
*/
( function() {
const INPUTSIZE_FULL = 'full';
const INPUTSIZE_COMPACT = 'compact';
const VALID_INPUTSIZES = [
INPUTSIZE_FULL,
INPUTSIZE_COMPACT
];
const TYPE_NUMBER = 'number';
const TYPE_STRING = 'string';
const VALID_TYPES = [
TYPE_NUMBER,
TYPE_STRING
];
/**
* Class CalculatorObject
*
* @param {Object} varData
* @param {Object} propertyData
* @returns {Object}
* @constructor
*/
function CalculatorObject( varData, propertyData ) {
if( propertyData ) {
if( propertyData.hasOwnProperty( 'required' ) ) {
for( var iRequiredProperty in propertyData.required ) {
var requiredProperty = propertyData.required[ iRequiredProperty ];
if( !varData.hasOwnProperty( requiredProperty ) ) {
console.error( 'Missing required property "' + requiredProperty + '"' );
console.log( varData );
return null;
}
this[ requiredProperty ] = varData[ requiredProperty ];
}
}
if( propertyData.hasOwnProperty( 'optional' ) ) {
for( var iOptionalProperty in propertyData.optional ) {
var optionalProperty = propertyData.optional[ iOptionalProperty ];
if( varData.hasOwnProperty( optionalProperty ) ) {
this[ optionalProperty ] = varData[ optionalProperty ];
} else {
this[ optionalProperty ] = null;
}
}
}
}
}
/**
* Class CalculatorVariable
* @param {Object} varData
* @returns {CalculatorVariable}
* @constructor
*/
function CalculatorVariable( varData ) {
var propertyData = {
required: [
'id',
'name',
'type'
],
optional: [
'abbreviation',
'defaultValue',
'maxLength',
'options',
'units'
]
};
CalculatorObject.call( this, varData, propertyData );
if( VALID_TYPES.indexOf( this.type ) === -1 ) {
throw new Error( 'Invalid type "' + this.type + '" for variable "' + this.id + '"' );
}
this.value = null;
}
CalculatorVariable.prototype = Object.create( CalculatorObject.prototype );
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 value = this.getValue();
// Create the input container
var $inputContainer = $( 'div', {
class: 'col-auto calculator-container-input calculator-container-input-' + this.getName()
} );
if( this.getType() === TYPE_NUMBER ) {
// Set the input id
var inputId = 'calculator-input-' + this.getId();
// Initialize label attributes
var labelAttributes = {
for: inputId,
text: this.getName()
};
// Initialize input options
var inputAttributes = {
id: inputId,
class: 'form-control calculator-input-text',
type: 'text',
inputmode: 'decimal',
placeholder: this.getName(),
value: typeof value === 'object' ? value.toNumber() : value
};
// Configure additional options
if( this.hasOwnProperty( 'maxLength' ) ) {
inputAttributes.maxlength = this.maxLength;
}
if( options.size === INPUTSIZE_COMPACT ) {
// Only use label for screen readers
labelAttributes.class = 'sr-only';
inputAttributes.class = inputAttributes.class + ' calculator-input-text-compact';
// Switch the placeholder to the abbreviation if defined
if( this.hasOwnProperty( 'abbreviation' ) ) {
inputAttributes.placeholder = this.abbreviation;
}
// Set the size of the input to the maxlength if defined
if( inputAttributes.hasOwnProperty( 'maxlength' ) ) {
inputAttributes.size = this.maxLength;
}
}
// Add the input id to the list of classes
inputAttributes.class = inputAttributes.class + ' ' + inputId;
// Create the input and add handlers
var $input = $( 'input', inputAttributes )
.on( 'change', function() {
console.log( $( this ).val() );
} );
// Create the input label and append to the container
$inputContainer.append( $( 'label', labelAttributes ) );
// Create the input group
var $inputGroup = $( 'div', {
class: 'input-group'
} ).append( $input );
// If the variable has units, create the units input
if( this.hasUnits() ) {
var unitsId = inputId + '-units';
var unitsValue = typeof value === 'object' ? value.formatUnits() : null;
// Create the units container
var $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 );
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( this.hasOwnProperty( 'renderUnits' ) ) {
unitsOptionAttributes.text = this.renderUnits( units );
}
if( units === unitsValue ) {
unitsOptionAttributes.selected = true;
}
$unitsInput.append( $( 'option', unitsOptionAttributes ) );
}
$unitsContainer.append( $unitsInput );
$inputGroup.append( $unitsContainer );
}
$inputContainer.append( $inputGroup );
} else if( this.getType === TYPE_STRING ) {
}
return $inputContainer;
};
CalculatorVariable.prototype.getId = function() {
return this.id;
};
CalculatorVariable.prototype.getName = function() {
return this.name;
};
CalculatorVariable.prototype.getType = function() {
return this.type;
};
CalculatorVariable.prototype.getValue = function() {
return this.value;
};
CalculatorVariable.prototype.getValueString = function() {
return String( this.value );
};
CalculatorVariable.prototype.hasUnits = function() {
return this.units !== null;
};
CalculatorVariable.prototype.setValue = function( value ) {
if( this.type === 'number' ) {
if( typeof value !== 'object' ) {
value = math.unit( value );
}
if( this.units ) {
var valueUnits = value.formatUnits();
if( !valueUnits ) {
throw new Error( 'Could not set value for "' + this.name + '": Value must define units' );
} else if( this.units.indexOf( valueUnits ) === -1 ) {
throw new Error( 'Could not set value for "' + this.name + '": Units "' + valueUnits + '" are not valid for this variable' );
}
}
}
this.value = value;
return true;
};
mw.calculators = {
variables: {},
addVariables: function( variables ) {
for( var varName in variables ) {
if( mw.calculators.variables.hasOwnProperty( varName ) ) {
console.warn( 'Calculation variable "' + varName + '" already defined.' );
continue;
}
var varData = variables[ varName ];
varData.id = varName;
var calculationVariable = new CalculatorVariable( varData );
if( calculationVariable ) {
mw.calculators.variables[ varName ] = calculationVariable;
}
}
},
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( varName ) {
return 'calculators-var-' + varName;
},
getVariable: function( varName ) {
if( mw.calculators.variables.hasOwnProperty( varName ) ) {
return mw.calculators.variables[ varName ];
} else {
return null;
}
},
getValue: function( varName ) {
if( mw.calculators.variables.hasOwnProperty( varName ) ) {
return mw.calculators.variables[ varName ].getValue();
} else {
return null;
}
},
init: function() {
mw.calculators.defineUnits();
},
setValue: function( varName, value ) {
if( !mw.calculators.variables.hasOwnProperty( varName ) ) {
return false;
}
return mw.calculators.variables[ varName ].setValue( value );
}
};
mw.calculators.init();
}() );