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() { | ||
var COOKIE_EXPIRATION = 12 * 60 * 60; | |||
var TYPE_NUMBER = 'number'; | |||
var TYPE_STRING = 'string'; | |||
var VALID_TYPES = [ | |||
TYPE_NUMBER, | TYPE_NUMBER, | ||
TYPE_STRING | TYPE_STRING | ||
Line 719: | Line 719: | ||
mw.calculators.objectClasses.CalculatorCalculation.prototype.getLabelString = function() { | mw.calculators.objectClasses.CalculatorCalculation.prototype.getLabelString = function() { | ||
return mw.calculators.isMobile() && this.abbreviation ? this.abbreviation : this.name; | return mw.calculators.isMobile() && this.abbreviation ? this.abbreviation : this.name; | ||
}; | |||
mw.calculators.objectClasses.CalculatorCalculation.prototype.getValue = function() { | |||
if( !this.value ) { | |||
this.recalculate(); | |||
} | |||
return this.value; | |||
}; | }; | ||
Revision as of 14:26, 8 August 2021
/** * @author Chris Rishel */ ( function() { var COOKIE_EXPIRATION = 12 * 60 * 60; var TYPE_NUMBER = 'number'; var TYPE_STRING = 'string'; var 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 ) { for( var calculatorId in calculatorData ) { calculatorData[ calculatorId ].module = moduleId; } 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; } try { math.createUnit( unitsId, { aliases: units[ unitsId ].aliases, baseName: units[ unitsId ].baseName, definition: units[ unitsId ].definition, prefixes: units[ unitsId ].prefixes, offset: units[ unitsId ].offset, } ); } catch( e ) { console.warn( e.message ); } 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 ]; if( typeof objectId === 'string' ) { 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( variableId ) { return 'calculators-var-' + variableId; }, 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, '' ) .replace( /(\^(\d+))/g, '<sup>$2</sup>' ); 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( variableId ) { if( mw.calculators.variables.hasOwnProperty( variableId ) ) { return mw.calculators.variables[ variableId ]; } 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( variableId, value ) { if( !mw.calculators.variables.hasOwnProperty( variableId ) ) { return false; } if( mw.calculators.variables[ variableId ].setValue( value ) ) { mw.cookie.set( mw.calculators.getCookieKey( variableId ), 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 variableId = this.id; var inputContainerAttribs = { class: 'form-group calculator-container-input' }; inputContainerAttribs.class = inputContainerAttribs.class + ' calculator-container-input-' + variableId; // Create the input container var $inputContainer = $( '<div>', inputContainerAttribs ); // Set the input id var inputId = 'calculator-input-' + variableId; // 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() ? this.value.toNumber() : this.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() ? this.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( variableId, 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( variableId, 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.options; var selectAttributes = { id: inputId, class: 'custom-select calculator-input-select' }; var $select = $( '<select>', selectAttributes ) .on( 'change', function() { mw.calculators.setValue( variableId, $( this ).val() ); } ); for( var iVarOption in varOptions ) { var varOption = varOptions[ iVarOption ]; var optionAttributes = { value: varOption, text: varOption }; if( varOption === this.value ) { optionAttributes.selected = true; } $select.append( $( '<option>', optionAttributes ) ); } $inputContainer.append( $select ); } } return $inputContainer; }; mw.calculators.objectClasses.CalculatorVariable.prototype.getLabelString = function() { return mw.calculators.isMobile() && this.abbreviation ? this.abbreviation : this.name; }; 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.id + '": Value must define units' ); } else if( this.units.indexOf( valueUnits ) === -1 ) { throw new Error( 'Could not set value for "' + this.id + '": 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.id + '": 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', 'description', '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.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.getValue = function() { if( !this.value ) { this.recalculate(); } return this.value; }; mw.calculators.objectClasses.CalculatorCalculation.prototype.getValueString = function() { if( this.message ) { return this.message; } else if( typeof this.value === 'object' && this.value.hasOwnProperty( 'value' ) ) { // format() will convert the value to the most visually appealing units (e.g. 5200 mL becomes 5.2 L) // We then want to turn that back into a math object. var value = math.unit( this.value.format() ); var units = value.formatUnits(); var number = value.toNumber(); var digits = ( this.value.formatUnits() === units && this.digits !== null ) ? this.digits : 1; var valueString = String( number.toFixed( digits ) ); if( units ) { valueString = valueString + ' ' + mw.calculators.getUnitsString( value ); } return valueString; } else { return String( this.value ); } }; mw.calculators.objectClasses.CalculatorCalculation.prototype.hasInfo = function() { return this.description || 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, variableId, 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 ) { variableId = this.data.variables.required[ iRequiredVariable ]; variable = mw.calculators.getVariable( variableId ); if( !variable ) { throw new Error( 'Invalid required variable "' + variableId + '" for calculation "' + this.id + '"' ); } else if( !variable.hasValue() ) { if( missingRequiredData ) { missingRequiredData = missingRequiredData + ', '; } missingRequiredData = missingRequiredData + variable.getLabelString(); } else { data[ variableId ] = variable.value; } } if( missingRequiredData ) { this.message = missingRequiredData + ' required'; return false; } for( var iOptionalVariable in this.data.variables.optional ) { variableId = this.data.variables.optional[ iOptionalVariable ]; variable = mw.calculators.getVariable( variableId ); if( !variable ) { throw new Error( 'Invalid optional variable "' + variableId + '" for calculation "' + this.id + '"' ); } data[ variableId ] = variable.hasValue() ? variable.value : 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.value : 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 ) { 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: '#' + calculation.getContainerClass() + '-info', role: 'button', 'aria-expanded': 'false', 'aria-controls': calculation.getContainerClass() + '-info' } ) .append( $( '<i>', { class: 'far fa-question-circle' } ) ); } var labelHtml = calculation.getLabelHtml(); if( isTable ) { if( calculation.hasInfo() ) { labelHtml += $( '<span>', { class: 'calculator-calculation-column-label-info' } ).append( $infoButton )[ 0 ].outerHTML; } $( this ) .append( $( '<th>', { html: labelHtml } ) ) .append( $( '<td>', { class: 'calculator-calculation-column-value', html: valueString } ) ); } else { $( this ) .append( labelHtml + $infoButton[ 0 ].outerHTML + ': ' + valueString ); } if( calculation.hasInfo() ) { var infoHtml = ''; if( calculation.description ) { infoHtml += $( '<p>', { html: calculation.description } )[ 0 ].outerHTML; } if( calculation.formula ) { infoHtml += $( '<span>', { class: calculation.getContainerClass() + '-formula' } )[ 0 ].outerHTML; var api = new mw.Api(); api.parse( calculation.formula ).then( function( result ) { $( '.' + calculation.getContainerClass() + '-formula' ).html( result ); } ); } if( calculation.references.length ) { var $references = $( '<ol>' ); for( var iReference in calculation.references ) { $references.append( $( '<li>', { text: calculation.references[ iReference ] } ) ); } infoHtml += $references[ 0 ].outerHTML; } var infoContainerId = calculation.getContainerClass() + '-info'; var $infoContainer = $( '#' + infoContainerId ); if( $infoContainer.length ) { $infoContainer.empty(); } if( isTable ) { $infoContainer = $( '<tr>', { id: infoContainerId, class: 'collapse' } ) .append( $( '<td>', { colspan: 2 } ).append( infoHtml ) ); } else { $infoContainer = $( '<div>', { id: infoContainerId, class: 'collapse' } ).append( infoHtml ); } $( this ).after( $infoContainer ); } 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: 2 } ).append( inputGroup ) ); } else { $variablesContainer = $( '<div>', { class: variablesContainerClass } ).append( inputGroup ); } $( this ).after( $variablesContainer ); missingVariableInputs = []; } } ); if( typeof this.onRendered === 'function' ) { this.onRendered( $calculationContainer ); } }; /** * Class CalculatorCalculator * @param {Object} propertyValues * @returns {mw.calculators.objectClasses.CalculatorCalculator} * @constructor */ mw.calculators.objectClasses.CalculatorCalculator = function( propertyValues ) { var properties = { required: [ 'id', 'module', '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.module + '-' + 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(); }() );