Difference between revisions of "MediaWiki:Gadget-calculator-drugs-core.js"

From WikiAnesthesia
Line 3: Line 3:
  */
  */
( function() {
( function() {
    var DEFAULT_DRUG_COLOR = 'default';
    var DEFAULT_DRUG_POPULATION = 'general';
    var DEFAULT_DRUG_ROUTE = 'iv';
    mw.calculators.isValueDependent = function( value, variableId ) {
        // This may need generalized to support other variables in the future
        if( variableId === 'weight' ) {
            return value && value.formatUnits().match( /\/[\s(]*?kg/ );
        } else {
            throw new Error( 'Dependence "' + variableId + '" not supported by isValueDependent' );
        }
    };
     /**
     /**
     * DrugColor data
     * Define units
     */
     */
     mw.calculators.addDrugColors( {
     mw.calculators.addUnitsBases( {
         anticholinergic: {
         concentration: {
             primaryColor: '#9fc96b'
             toString: function( units ) {
                units = units.replace( ' pct', '%' );
                units = units.replace( 'ug', 'mcg' );
 
                return units;
            }
         },
         },
         benzodiazepine: {
         mass: {
             primaryColor: '#f57921'
             toString: function( units ) {
                units = units.replace( 'ug', 'mcg' );
 
                return units;
            }
        }
    } );
 
    mw.calculators.addUnits( {
        mcg: {
            baseName: 'mass',
            definition: '1 ug'
         },
         },
         benzodiazepineReversal: {
         pct: {
             parentColor: 'benzodiazepine',
             baseName: 'concentration',
             striped: true
             definition: '10 mg/mL'
         },
         },
         cardiovascularAgonist: {
         units: {
             primaryColor: '#e1c9df'
             baseName: 'mass_abstract',
        },
             aliases: [
        cardiovascularAntagonist: {
                'unit'
            parentColor: 'cardiovascularAgonist',
             ]
            striped: true
        },
        default: {
            primaryColor: '#fff',
             highlightColor: '#fff'
        },
        desflurane: {
            primaryColor: '#0072ae'
        },
        enflurane: {
             primaryColor: '#f9a23b'
        },
        epinephrine: {
            parentColor: 'cardiovascularAgonist',
            highlightColor: '#000'
        },
        halothane: {
            primaryColor: '#b20107'
        },
        isoflurane: {
            primaryColor: '#92278f'
        },
        localAnesthetic: {
            primaryColor: '#b6b2a9'
        },
        neuromuscularBlocker: {
            primaryColor: '#f15563'
        },
        neuromuscularBlockerReversal: {
            parentColor: 'neuromuscularBlocker',
            striped: true
        },
        nitrousOxide: {
            primaryColor: '#004f7c'
        },
        opioid: {
            primaryColor: '#6bc8ea'
        },
        opioidReversal: {
            parentColor: 'opioid',
            striped: true
        },
        sedativeHypnotic: {
            primaryColor: '#f7e20a'
        },
        sevoflurane: {
            primaryColor: '#ffe715'
         },
         },
         succinylcholine: {
         vial: {
             parentColor: 'neuromuscularBlocker',
             baseName: 'volume_abstract'
            highlightColor: '#000'
         }
         }
     } );
     } );
Line 79: Line 60:


     /**
     /**
     * DrugPopulation data
     * DrugColor
     */
     */
     mw.calculators.addDrugPopulations( {
     mw.calculators.drugColors = {};
        general: {
 
            name: 'General',
    mw.calculators.addDrugColors = function( drugColorData ) {
        },
         var drugColors = mw.calculators.createCalculatorObjects( 'DrugColor', drugColorData );
        general120kg: {
 
            name: 'General (≥120kg)',
         for( var drugColorId in drugColors ) {
            abbreviation: '≥120kg',
             mw.calculators.drugColors[ drugColorId ] = drugColors[ drugColorId ];
            variables: {
                weight: {
                    min: '120 kgwt'
                }
            }
         },
        neonatal: {
            name: 'Neonatal',
            variables: {
                age: {
                    max: '0 yo'
                }
            }
         },
        pediatric: {
             name: 'Pediatric',
            variables: {
                age: {
                    min: '0 yo',
                    max: '17.9 yo'
                }
            }
        },
        geriatric: {
            name: 'Geriatric',
            variables: {
                age: {
                    min: '65 yo'
                }
            }
         }
         }
     } );
     };
 


    mw.calculators.getDrugColor = function( drugColorId ) {
        if( mw.calculators.drugColors.hasOwnProperty( drugColorId ) ) {
            return mw.calculators.drugColors[ drugColorId ];
        } else {
            return null;
        }
    };


     /**
     /**
     * DrugRoute data
     * Class DrugColor
    * @param {Object} propertyValues
    * @returns {mw.calculators.objectClasses.DrugColor}
    * @constructor
     */
     */
     mw.calculators.addDrugRoutes( {
     mw.calculators.objectClasses.DrugColor = function( propertyValues ) {
         iv: {
         var properties = {
             name: 'Intravenous',
             required: [
             abbreviation: 'IV',
                'id'
            default: true
            ],
        },
             optional: [
        im: {
                'parentColor',
            name: 'Intramuscular',
                'primaryColor',
            abbreviation: 'IM'
                'highlightColor',
         },
                'striped'
         po: {
            ]
            name: 'Oral',
         };
             abbreviation: 'PO'
 
         },
        mw.calculators.objectClasses.CalculatorObject.call( this, properties, propertyValues );
         pr: {
 
             name: 'Rectal',
         this.parentColor = this.parentColor || this.id === DEFAULT_DRUG_COLOR ? this.parentColor : DEFAULT_DRUG_COLOR;
            abbreviation: 'PR'
    };
 
    mw.calculators.objectClasses.DrugColor.prototype = Object.create( mw.calculators.objectClasses.CalculatorObject.prototype );
 
    mw.calculators.objectClasses.DrugColor.prototype.getParentDrugColor = function() {
        if( !this.parentColor ) {
             return null;
         }
 
        var parentDrugColor = mw.calculators.getDrugColor( this.parentColor );
 
         if( !parentDrugColor ) {
             throw new Error( 'Parent drug color "' + this.parentColor + '" not found for drug color "' + this.id + '"' );
         }
         }
    } );


        return parentDrugColor;
    };
    mw.calculators.objectClasses.DrugColor.prototype.getHighlightColor = function() {
        if( this.highlightColor ) {
            return this.highlightColor;
        } else if( this.parentColor ) {
            return this.getParentDrugColor().getHighlightColor();
        }
    };


    mw.calculators.objectClasses.DrugColor.prototype.getPrimaryColor = function() {
        if( this.primaryColor ) {
            return this.primaryColor;
        } else if( this.parentColor ) {
            return this.getParentDrugColor().getPrimaryColor();
        }
    };


    /**
     mw.calculators.objectClasses.DrugColor.prototype.isStriped = function() {
    * DrugIndication data
         if( this.striped !== null ) {
    */
             return this.striped;
     mw.calculators.addDrugIndications( {
         } else if( this.parentColor ) {
         abxProphylaxis: {
             return this.getParentDrugColor().isStriped();
             name: 'Antimicrobial prophylaxis',
            abbreviation: 'Abx.'
         },
        acls: {
             name: 'ACLS',
            abbreviation: 'ACLS'
        },
        analgesia: {
            name: 'Analgesia',
            abbreviation: 'Analgesia'
        },
        anaphylaxis: {
            name: 'Anaphylaxis',
            abbreviation: 'Anaphylaxis'
        },
        anxiolysis: {
            name: 'Anxiolysis',
            abbreviation: 'Anxiety'
        },
        bradycardia: {
            name: 'Bradycardia',
            abbreviation: 'Bradycardia'
        },
        generalAnesthesia: {
            name: 'General anesthesia',
            abbreviation: 'GA'
        },
        hypotension: {
            name: 'Hypotension',
            abbreviation: 'Hypoten.'
        },
        hypocalcemia: {
            name: 'Hypocalcemia',
            abbreviation: 'Hypo Ca<sup>2+</sup>'
        },
        intubation: {
            name: 'Intubation',
            abbreviation: 'Intubation'
        },
        malignantHyperthermia: {
            name: 'Malignant hyperthermia',
            abbreviation: 'MH'
        },
        neuromuscularBlockade: {
            name: 'Neuromuscular blockade',
            abbreviation: 'NMB'
        },
        neuromuscularBlockadeReversal: {
            name: 'Neuromuscular blockade reversal',
            abbreviation: 'NMB reversal'
        },
        mac: {
            name: 'Monitored anesthesia care',
            abbreviation: 'MAC'
        },
        ponv: {
            name: 'Postoperative nausea & vomiting',
            abbreviation: 'PONV'
        },
        tiva: {
            name: 'Total intravenous anesthesia',
            abbreviation: 'TIVA'
         }
         }
     } );
     };
 
 






     /**
     /**
     * Drug data
     * DrugPopulation
     */
     */
    mw.calculators.drugPopulations = {};
    mw.calculators.addDrugPopulations = function( drugPopulationData ) {
        var drugPopulations = mw.calculators.createCalculatorObjects( 'DrugPopulation', drugPopulationData );
        for( var drugPopulationId in drugPopulations ) {
            mw.calculators.drugPopulations[ drugPopulationId ] = drugPopulations[ drugPopulationId ];
        }
    };
    mw.calculators.getDrugPopulation = function( drugPopulationId ) {
        if( mw.calculators.drugPopulations.hasOwnProperty( drugPopulationId ) ) {
            return mw.calculators.drugPopulations[ drugPopulationId ];
        } else {
            return null;
        }
    };






     /**
     /**
     * Acetaminophen
     * Class DrugPopulation
    * @param {Object} propertyValues
    * @returns {mw.calculators.objectClasses.DrugPopulation}
    * @constructor
     */
     */
     mw.calculators.addDrugs( {
     mw.calculators.objectClasses.DrugPopulation = function( propertyValues ) {
         acetaminophen: {
         var properties = {
             name: 'Acetaminophen',
             required: [
            dosages: [
                 'id',
                 {
                 'name'
                    indication: 'analgesia',
                    population: 'general',
                    description: 'Max dose 4 g/day. Use caution in hepatic impairment.',
                    dose: {
                        dose: '1 g'
                    }
                }, {
                    indication: 'analgesia',
                    population: 'general',
                    route: 'po',
                    description: 'Max dose 4 g/day. Use caution in hepatic impairment.',
                    dose: {
                        min: '650 mg',
                        max: '1 g'
                    }
                }, {
                    indication: 'analgesia',
                    population: 'general',
                    route: 'pr',
                    description: 'Max dose 4 g/day. Use caution in hepatic impairment.',
                    dose: {
                        min: '650 mg',
                        max: '1 g'
                    }
                }, {
                    indication: 'analgesia',
                    population: 'pediatric',
                    description: 'Max 75 mg/kg/day or 4 g/day (whichever is less). Use caution in hepatic impairment.',
                    dose: {
                        dose: '15 mg/kg',
                        absoluteMax: '1 g'
                    }
                }, {
                    indication: 'analgesia',
                    population: 'pediatric',
                    route: 'po',
                    description: 'Max 75 mg/kg/day or 4 g/day (whichever is less). Use caution in hepatic impairment.',
                    dose: {
                        dose: '15 mg/kg',
                        absoluteMax: '1 g'
                    }
                 }, {
                    indication: 'analgesia',
                    population: 'pediatric',
                    route: 'pr',
                    description: 'Max 100 mg/kg/day or 4 g/day (whichever is less). Use caution in hepatic impairment.',
                    references: [
                        'Patrick K. Birmingham, AF1-0567, Michael J. Tobin, Dennis M. Fisher, Thomas K. Henthorn, Steven C. Hall, Charles J. Coté; Initial and Subsequent Dosing of Rectal Acetaminophen in Children: A 24-Hour Pharmacokinetic Study of New Dose Recommendations. Anesthesiology 2001; 94:385–389 doi: https://doi.org/10.1097/00000542-200103000-00005'
                    ],
                    dose: [
                        {
                            name: 'Load',
                            dose: '40 mg/kg',
                            absoluteMax: '1 g'
                        }, {
                            name: 'Maintenance',
                            dose: '20 mg/kg',
                            absoluteMax: '1 g'
                        }
                    ]
                }
             ],
             ],
             preparations: [
             optional: [
                 {
                 'abbreviation',
                     concentration: '10 mg/mL'
                'variables'
            ]
        };
 
        mw.calculators.objectClasses.CalculatorObject.call( this, properties, propertyValues );
 
        if( this.variables ) {
            for( var variableId in this.variables ) {
                if( !mw.calculators.getVariable( variableId ) ) {
                     throw new Error( 'DrugPopulation variable "' + variableId + '" not defined' );
                 }
                 }
             ]
 
                this.variables[ variableId ].min = this.variables[ variableId ].hasOwnProperty( 'min' ) ?
                    math.unit( this.variables[ variableId ].min ) : null;
 
                this.variables[ variableId ].max = this.variables[ variableId ].hasOwnProperty( 'max' ) ?
                    math.unit( this.variables[ variableId ].max ) : null;
             }
        } else {
            this.variables = {};
        }
    };
 
    mw.calculators.objectClasses.DrugPopulation.prototype = Object.create( mw.calculators.objectClasses.CalculatorObject.prototype );
 
    mw.calculators.objectClasses.DrugPopulation.prototype.getCalculationData = function() {
        var inputData = new mw.calculators.objectClasses.CalculationData();
 
        for( var variableId in this.variables ) {
            inputData.variables.required.push( variableId );
        }
 
        return inputData;
    };
 
    mw.calculators.objectClasses.DrugPopulation.prototype.getCalculationDataScore = function( dataValues ) {
        // A return value of -1 indicates the data did not match the population definition
 
        for( var variableId in this.variables ) {
            if( !dataValues.hasOwnProperty( variableId ) ) {
                return -1;
            }
 
            if( this.variables[ variableId ].min &&
                ( !dataValues[ variableId ] ||
                    !math.largerEq( dataValues[ variableId ], this.variables[ variableId ].min ) ) ) {
                return -1;
            }
 
            if( this.variables[ variableId ].max &&
                ( !dataValues[ variableId ] ||
                    !math.smallerEq( dataValues[ variableId ], this.variables[ variableId ].max ) ) ) {
                return -1;
            }
         }
         }
     } );
 
        // If the data matches the population definition, the score corresponds to the number of variables in the
        // population definition. This should roughly correspond to the specificity of the population.
        return Object.keys( this.variables ).length;
     };
 
    mw.calculators.objectClasses.DrugPopulation.prototype.toString = function() {
        return mw.calculators.isMobile() && this.abbreviation ? this.abbreviation : this.name;
    };






     /**
     /**
     * Atropine
     * DrugRoute
     */
     */
     mw.calculators.addDrugs( [
     mw.calculators.drugRoutes = {};
        {
 
            id: 'atropine',
    mw.calculators.addDrugRoutes = function( drugRouteData ) {
            name: 'Atropine',
        var drugRoutes = mw.calculators.createCalculatorObjects( 'DrugRoute', drugRouteData );
            color: 'anticholinergic',
 
            description: 'Low doses may cause paradoxical bradycardia',
        for( var drugRouteId in drugRoutes ) {
            dosages: [
             mw.calculators.drugRoutes[ drugRouteId ] = drugRoutes[ drugRouteId ];
                {
                    indication: 'bradycardia',
                    population: 'general',
                    description: 'Repeat as needed to a maximum total dose of 3 mg',
                    dose: {
                        min: '0.5 mg',
                        max: '1 mg',
                        frequency: 'q3-5m'
                    }
                }, {
                    indication: 'bradycardia',
                    population: 'pediatric',
                    description: 'Repeat as needed to a maximum total dose of 1 mg',
                    dose: {
                        min: '10 mcg/kg',
                        max: '20 mcg/kg',
                        absoluteMin: '40 mcg',
                        frequency: 'q3-5m'
                    }
                }, {
                    indication: 'acls',
                    population: 'general',
                    description: 'Repeat as needed to a maximum total dose of 3 mg',
                    dose: {
                        dose: '1 mg',
                        frequency: 'q3-5m'
                    }
                }, {
                    indication: 'acls',
                    population: 'pediatric',
                    description: 'Repeat as needed to a maximum total dose of 1 mg',
                    dose: {
                        dose: '20 mcg/kg',
                        absoluteMin: '100 mcg',
                        absoluteMax: '500 mcg',
                        frequency: 'q3-5m'
                    }
                }
             ],
            preparations: [
                {
                    concentration: '0.1 mg/mL'
                }, {
                    concentration: '0.4 mg/mL',
                    default:true
                }
            ]
         }
         }
     ] );
     };
 


    mw.calculators.getDrugRoute = function( drugRouteId ) {
        if( mw.calculators.drugRoutes.hasOwnProperty( drugRouteId ) ) {
            return mw.calculators.drugRoutes[ drugRouteId ];
        } else {
            return null;
        }
    };


     /**
     /**
     * Calcium chloride
     * Class DrugRoute
    * @param {Object} propertyValues
    * @returns {mw.calculators.objectClasses.DrugRoute}
    * @constructor
     */
     */
     mw.calculators.addDrugs( [
     mw.calculators.objectClasses.DrugRoute = function( propertyValues ) {
         {
         var properties = {
             id: 'calciumChloride',
             required: [
            name: 'Calcium chloride',
                'id',
            description: '<ul><li>Administer via central line</li><li>A 10 mL ampule of calcium chloride 100 mg/mL contains 272 mg of elemental calcium</li></ul>',
                 'name'
            references: [
                 'French S, Subauste J, Geraci S. Calcium abnormalities in hospitalized patients. South Med J. 2012 Apr;105(4):231-7. doi: 10.1097/SMJ.0b013e31824e1737. PMID: 22475676.'
             ],
             ],
             dosages: [
             optional: [
                 {
                 'abbreviation',
                    indication: 'hypocalcemia',
                 'default'
                    population: 'general',
                    dose: {
                        min: '500 mg',
                        max: '1 g',
                        duration: '5-10 min'
                    }
                }, {
                    indication: 'hypocalcemia',
                    population: 'pediatric',
                    dose: {
                        min: '10 mg/kg',
                        max: '20 mg/kg',
                        absoluteMax: '1 g',
                        duration: '5-10 min'
                    }
                 }
            ],
            preparations: [
                {
                    concentration: '100 mg/mL'
                }
             ]
             ]
         }
         };
     ] );
 
        mw.calculators.objectClasses.CalculatorObject.call( this, properties, propertyValues );
    };
 
     mw.calculators.objectClasses.DrugRoute.prototype = Object.create( mw.calculators.objectClasses.CalculatorObject.prototype );
 
    mw.calculators.objectClasses.DrugRoute.prototype.toString = function() {
        return mw.calculators.isMobile() && this.abbreviation ? this.abbreviation : this.name;
    };
 
 
 
 






     /**
     /**
     * Calcium gluconate
     * DrugIndication
     */
     */
     mw.calculators.addDrugs( [
     mw.calculators.drugIndications = {};
        {
 
            id: 'calciumGluconate',
    mw.calculators.addDrugIndications = function( drugIndicationData ) {
            name: 'Calcium gluconate',
        var drugIndications = mw.calculators.createCalculatorObjects( 'DrugIndication', drugIndicationData );
            description: '<ul><li>Can administer peripherally</li><li>A 10 mL ampule of calcium gluconate 100 mg/mL contains 93 mg of elemental calcium</li></ul>',
 
            references: [
        for( var drugIndicationId in drugIndications ) {
                'French S, Subauste J, Geraci S. Calcium abnormalities in hospitalized patients. South Med J. 2012 Apr;105(4):231-7. doi: 10.1097/SMJ.0b013e31824e1737. PMID: 22475676.'
             mw.calculators.drugIndications[ drugIndicationId ] = drugIndications[ drugIndicationId ];
            ],
            dosages: [
                {
                    indication: 'hypocalcemia',
                    population: 'general',
                    dose: {
                        min: '1 g',
                        max: '2 g',
                        duration: '5-10 min'
                    }
                }, {
                    indication: 'hypocalcemia',
                    population: 'pediatric',
                    dose: {
                        dose: '30 mg/kg',
                        absoluteMax: '2 g',
                        duration: '5-10 min'
                    }
                }
             ],
            preparations: [
                {
                    concentration: '100 mg/mL'
                }
            ]
         }
         }
     ] );
     };
 


    mw.calculators.getDrugIndication = function( drugIndicationId ) {
        if( mw.calculators.drugIndications.hasOwnProperty( drugIndicationId ) ) {
            return mw.calculators.drugIndications[ drugIndicationId ];
        } else {
            return null;
        }
    };


     /**
     /**
     * Cefazolin
     * Class DrugIndication
    * @param {Object} propertyValues
    * @returns {mw.calculators.objectClasses.DrugIndication}
    * @constructor
     */
     */
     mw.calculators.addDrugs( {
     mw.calculators.objectClasses.DrugIndication = function( propertyValues ) {
         cefazolin: {
         var properties = {
             name: 'Cefazolin',
             required: [
            dosages: [
                 'id',
                 {
                 'name'
                    indication: 'abxProphylaxis',
                    population: 'general',
                    dose: {
                        dose: '2 g',
                        frequency: 'q4h'
                    }
                 }, {
                    indication: 'abxProphylaxis',
                    population: 'general120kg',
                    dose: {
                        dose: '3 g',
                        frequency: 'q4h'
                    }
                }, {
                    indication: 'abxProphylaxis',
                    population: 'pediatric',
                    dose: {
                        dose: '30 mg/kg',
                        frequency: 'q4h'
                    }
                }
             ],
             ],
             preparations: [
             optional: [
                 {
                 'abbreviation',
                    concentration: '1 g/vial'
                 'default'
                 }
             ]
             ]
         }
         };
     } );
 
        mw.calculators.objectClasses.CalculatorObject.call( this, properties, propertyValues );
     };
 
    mw.calculators.objectClasses.DrugIndication.prototype = Object.create( mw.calculators.objectClasses.CalculatorObject.prototype );
 
    mw.calculators.objectClasses.DrugIndication.prototype.toString = function() {
        return mw.calculators.isMobile() && this.abbreviation ? this.abbreviation : this.name;
    };
 
 






     /**
     /**
     * Cisatracurium
     * Drug
     */
     */
     mw.calculators.addDrugs( {
    mw.calculators.drugs = {};
         cisatracurium: {
 
             name: 'Cisatracurium',
     mw.calculators.addDrugs = function( drugData ) {
             color: 'neuromuscularBlocker',
         var drugs = mw.calculators.createCalculatorObjects( 'Drug', drugData );
             dosages: [
 
                 {
        for( var drugId in drugs ) {
                     indication: 'neuromuscularBlockade',
             mw.calculators.drugs[ drugId ] = drugs[ drugId ];
                     population: 'general',
 
                     dose: {
            var drugDosageCalculationId = mw.calculators.getDrugDosageCalculationId( drugId );
                        dose: '0.2 mg/kg'
             var drugDosageCalculation = mw.calculators.getCalculation( drugDosageCalculationId );
                    }
 
                 }
             if( !drugDosageCalculation ) {
             ],
                var calculationData = {};
            preparations: [
 
                {
                 calculationData[ drugDosageCalculationId ] = {
                    concentration: '2 mg/mL'
                     calculate: mw.calculators.objectClasses.DrugDosageCalculation.prototype.calculate,
                }, {
                     drug: drugId,
                    concentration: '20 mg/mL'
                     type: 'drug'
                }
                };
             ]
 
                mw.calculators.addCalculations( calculationData, 'DrugDosageCalculation' );
 
                 drugDosageCalculation = mw.calculators.getCalculation( drugDosageCalculationId );
            }
 
             drugDosageCalculation.setDependencies();
        }
    };
 
    mw.calculators.addDrugDosages = function( drugId, drugDosageData ) {
        var drug = mw.calculators.getDrug( drugId );
 
        if( !drug ) {
            throw new Error( 'DrugDosage references drug "' + drugId + '" which is not defined' );
        }
 
        drug.addDosages( drugDosageData );
 
        // Update calculation dependencies
        var drugDosageCalculation = mw.calculators.getCalculation( mw.calculators.getDrugDosageCalculationId( drugId ) );
 
        drugDosageCalculation.updateVariables();
        drugDosageCalculation.setDependencies();
    };
 
    mw.calculators.getDrug = function( drugId ) {
        if( mw.calculators.drugs.hasOwnProperty( drugId ) ) {
             return mw.calculators.drugs[ drugId ];
        } else {
            return null;
         }
         }
     } );
     };






     /**
     /**
     * Dantrolene
     * Class Drug
    * @param {Object} propertyValues
    * @returns {mw.calculators.objectClasses.Drug}
    * @constructor
     */
     */
     mw.calculators.addDrugs( {
     mw.calculators.objectClasses.Drug = function( propertyValues ) {
         dantrolene: {
        mw.calculators.objectClasses.CalculatorObject.call( this, this.getProperties(), propertyValues );
             name: 'Dantrolene',
 
             dosages: [
        if( !this.color ) {
                {
            this.color = DEFAULT_DRUG_COLOR;
                    indication: 'malignantHyperthermia',
        }
                    population: 'general',
 
                    dose: {
         var color = mw.calculators.getDrugColor( this.color );
                        dose: '2.5 mg/kg'
 
                    },
        if( !color ) {
                     description: 'Repeat up to a cumulative dose of 10 mg/kg'
             throw new Error( 'Invalid drug color "' + this.color + '" for drug "' + this.id + '"' );
        }
 
        this.color = color;
 
        if( this.preparations ) {
            var preparationData = this.preparations;
 
            this.preparations = [];
 
            this.addPreparations( preparationData );
        } else {
            this.preparations = [];
        }
 
        if( this.dosages ) {
            var dosageData = this.dosages;
 
            this.dosages = [];
 
            this.addDosages( dosageData );
        } else {
             this.dosages = [];
        }
 
        this.references = this.references ? this.references : [];
    };
 
    mw.calculators.objectClasses.Drug.prototype = Object.create( mw.calculators.objectClasses.CalculatorObject.prototype );
 
    mw.calculators.objectClasses.Drug.prototype.addDosages = function( dosageData ) {
        var dosages = mw.calculators.createCalculatorObjects( 'DrugDosage', dosageData );
 
        for( var dosageId in dosages ) {
            dosages[ dosageId ].id = this.dosages.length;
 
            this.dosages.push( dosages[ dosageId ] );
        }
    };
 
    mw.calculators.objectClasses.Drug.prototype.addPreparations = function( preparationData ) {
        var preparations = mw.calculators.createCalculatorObjects( 'DrugPreparation', preparationData );
 
        for( var preparationId in preparations ) {
            preparations[ preparationId ].id = this.preparations.length;
 
            this.preparations.push( preparations[ preparationId ] );
        }
    };
 
    mw.calculators.objectClasses.Drug.prototype.getIndications = function() {
        var indications = [];
 
        for( var iDosage in this.dosages ) {
            if( this.dosages[ iDosage ].indication ) {
                indications.push( this.dosages[ iDosage ].indication );
            }
        }
 
        return indications.filter( mw.calculators.uniqueValues );
    };
 
    mw.calculators.objectClasses.Drug.prototype.getPopulations = function( indicationId ) {
        var populations = [];
 
        for( var iDosage in this.dosages ) {
            if( this.dosages[ iDosage ].population &&
                ( !indicationId || ( this.dosages[ iDosage ].indication && this.dosages[ iDosage ].indication.id === indicationId ) ) ) {
                populations.push( this.dosages[ iDosage ].population );
            }
        }
 
        return populations.filter( mw.calculators.uniqueValues );
    };
 
    mw.calculators.objectClasses.Drug.prototype.getRoutes = function( indicationId ) {
        var routes = [];
 
        for( var iDosage in this.dosages ) {
            if( this.dosages[ iDosage ].route &&
                ( !indicationId || ( this.dosages[ iDosage ].indication && this.dosages[ iDosage ].indication.id === indicationId ) ) ) {
                routes.push( this.dosages[ iDosage ].route );
            }
        }
 
        return routes.filter( mw.calculators.uniqueValues );
    };
 
    mw.calculators.objectClasses.Drug.prototype.getPreparations = function( excludeDilutionRequired ) {
        var preparations = this.preparations.filter( mw.calculators.uniqueValues );
 
        if( excludeDilutionRequired ) {
            for( var iPreparation in preparations ) {
                if( preparations[ iPreparation ].dilutionRequired ) {
                     delete preparations[ iPreparation ];
                 }
                 }
            }
        }
        return preparations;
    };
    mw.calculators.objectClasses.Drug.prototype.getProperties = function() {
        return {
            required: [
                'id',
                'name'
             ],
             ],
             preparations: [
             optional: [
                 {
                 'color',
                    concentration: '250 mg/vial'
                'description',
                 }, {
                 'dosages',
                    concentration: '20 mg/vial'
                'preparations',
                 }
                 'references'
             ]
             ]
         }
         };
     } );
     };
 
 






     /**
     /**
     * Dexmedetomidine
     * DrugPreparation
     */
     */
     mw.calculators.addDrugs( {
     mw.calculators.addDrugPreparations = function( drugId, drugPreparationData ) {
         dexmedetomidine: {
         var drug = mw.calculators.getDrug( drugId );
            name: 'Dexmedetomidine',
 
            color: 'sedativeHypnotic',
        if( !drug ) {
            dosages: [
             throw new Error( 'DrugPreparation references drug "' + drugId + '" which is not defined' );
                {
                    indication: 'mac',
                    population: 'general',
                    dose: [
                        {
                            name: 'Load',
                            dose: '1 mcg/kg',
                            duration: '10 min'
                        }, {
                            name: 'Maintenance',
                            min: '0.2 mcg/kg/hr',
                            max: '1 mcg/kg/hr'
                        }
                    ]
                }
             ],
            preparations: [
                {
                    concentration: '4 mcg/mL'
                }, {
                    concentration: '100 mcg/mL',
                    dilutionRequired: true
                }
            ]
         }
         }
     } );
 
        drug.addPreparations( drugPreparationData );
 
        var drugDosageCalculation = mw.calculators.getCalculation( mw.calculators.getDrugDosageCalculationId( drugId ) );
 
        drugDosageCalculation.recalculate();
     };






     /**
     /**
     * Dexamethasone
     * Class DrugPreparation
    * @param {Object} propertyValues
    * @returns {mw.calculators.objectClasses.DrugPreparation}
    * @constructor
     */
     */
     mw.calculators.addDrugs( [
     mw.calculators.objectClasses.DrugPreparation = function( propertyValues ) {
         {
         var properties = {
             id: 'dexamethasone',
             required: [
            name: 'Dexamethasone',
                 'id',
            dosages: [
                 'concentration'
                 {
                    indication: 'ponv',
                    population: 'general',
                    dose: {
                        min: '4 mg',
                        max: '8 mg'
                    }
                 }, {
                    indication: 'ponv',
                    population: 'pediatric',
                    dose: {
                        dose: '0.1 mg/kg',
                        absoluteMax: '10 mg'
                    }
                }
             ],
             ],
             preparations: [
             optional: [
                 {
                 'default',
                    concentration: '2 mg/mL'
                'dilutionRequired',
                 }
                 'commonDilution'
             ]
             ]
         }
         };
     ] );
 
        mw.calculators.objectClasses.CalculatorObject.call( this, properties, propertyValues );
 
 
        this.concentration = this.concentration.replace( 'mcg', 'ug' );
 
        this.concentration = math.unit( this.concentration );
    };
 
    mw.calculators.objectClasses.DrugPreparation.prototype = Object.create( mw.calculators.objectClasses.CalculatorObject.prototype );
 
     mw.calculators.objectClasses.DrugPreparation.prototype.getVolumeUnits = function() {
        // The units of concentration will always be of the form "mass / volume"
        // The regular expression matches all text leading up to the volume units
        return mw.calculators.getUnitsByBase( this.concentration ).volume;
    };
 
    mw.calculators.objectClasses.DrugPreparation.prototype.toString = function() {
        return mw.calculators.getValueString( this.concentration );
    };
 
 






     /**
     /**
     * Dobutamine
     * Class DrugDosage
    * @param {Object} propertyValues
    * @returns {mw.calculators.objectClasses.DrugDosage}
    * @constructor
     */
     */
     mw.calculators.addDrugs( {
     mw.calculators.objectClasses.DrugDosage = function( propertyValues ) {
         dobutamine: {
         mw.calculators.objectClasses.CalculatorObject.call( this, this.getProperties(), propertyValues );
             name: 'Dobutamine',
 
             color: 'cardiovascularAgonist',
        var drugIndication = mw.calculators.getDrugIndication( this.indication );
            description: 'First line vasopressor for cardiogenic shock with low cardiac output and maintained blood pressure',
 
            dosages: [
        if( !drugIndication ) {
                {
             throw new Error( 'Invalid indication "' + this.indication + '" for drug dosage' );
                    indication: 'hypotension',
        }
                    population: 'general',
 
                    references: [
        this.indication = drugIndication;
                        'Hollenberg SM. Vasoactive drugs in circulatory shock. Am J Respir Crit Care Med. 2011 Apr 1;183(7):847-55. doi: 10.1164/rccm.201006-0972CI. Epub 2010 Nov 19. PMID: 21097695.'
 
                    ],
        this.population = this.population ? this.population : DEFAULT_DRUG_POPULATION;
                    dose: {
 
                        min: '0.5 mcg/kg/min',
        var drugPopulation = mw.calculators.getDrugPopulation( this.population );
                        max: '20 mcg/kg/min'
 
                    }
        if( !drugPopulation ) {
                 }
             throw new Error( 'Invalid population "' + this.population + '" for drug dosage' );
        }
 
        this.population = drugPopulation;
 
        this.references = this.references ? this.references : [];
 
        this.route = this.route ? this.route : DEFAULT_DRUG_ROUTE;
 
        var drugRoute = mw.calculators.getDrugRoute( this.route );
 
        if( !drugRoute ) {
            throw new Error( 'Invalid route "' + this.route + '" for drug dosage' );
        }
 
        this.route = drugRoute;
 
        // Add the dose objects to the drug
        var drugDoseData = this.dose;
        this.dose = [];
 
        this.addDoses( drugDoseData );
    };
 
    mw.calculators.objectClasses.DrugDosage.prototype = Object.create( mw.calculators.objectClasses.CalculatorObject.prototype );
 
    mw.calculators.objectClasses.DrugDosage.prototype.addDoses = function( drugDoseData ) {
        // Each dosage can have one or more associated doses. Ensure this value is an array.
        if( !Array.isArray( drugDoseData ) ) {
            drugDoseData = [ drugDoseData ];
        }
 
        var doses = mw.calculators.createCalculatorObjects( 'DrugDose', drugDoseData );
 
        for( var doseId in doses ) {
            doses[ doseId ].id = this.dose.length;
 
            this.dose.push( doses[ doseId ] );
        }
    };
 
    mw.calculators.objectClasses.DrugDosage.prototype.getCalculationData = function() {
        var inputData = new mw.calculators.objectClasses.CalculationData();
 
        inputData = inputData.merge( this.population.getCalculationData() );
 
        for( var iDose in this.dose ) {
            inputData = inputData.merge( this.dose[ iDose ].getCalculationData() );
        }
 
        return inputData;
    };
 
    mw.calculators.objectClasses.DrugDosage.prototype.getProperties = function() {
        return {
            required: [
                'dose',
                'id',
                 'indication'
             ],
             ],
             preparations: [
             optional: [
                 {
                 'description',
                    concentration: '0.5 mg/mL'
                 'population',
                 }, {
                'route',
                    concentration: '12.5 mg/mL'
                 'references'
                 }
             ]
             ]
         }
         };
     } );
     };
 
    mw.calculators.objectClasses.DrugDosage.prototype.hasInfo = function() {
        return this.description;
    };
 
 






     /**
     /**
     * Dopamine
     * Class DrugDose
    * @param {Object} propertyValues
    * @returns {mw.calculators.objectClasses.DrugDose}
    * @constructor
     */
     */
     mw.calculators.addDrugs( {
     mw.calculators.objectClasses.DrugDose = function( propertyValues ) {
         dopamine: {
         mw.calculators.objectClasses.CalculatorObject.call( this, this.getProperties(), propertyValues );
             name: 'Dopamine',
 
            color: 'cardiovascularAgonist',
        if( this.weightCalculation ) {
             description: '<ul><li>Predominant receptor activation is dose-dependent:<ul><li><5 mcg/kg/min: Dopaminergic</li><li>5-10 mcg/kg/min: &beta;-1 adrenergic</li><li>>10 mcg/kg/min: &alpha;-1 adrenergic</li></ul></li><li>Low-dose dopamine should not be used for renal protective effect</li></ul>',
             var weightCalculationIds = this.weightCalculation;
             dosages: [
 
                {
             // weightCalculation property will contain references to the actual objects, so reinitialize
                    indication: 'hypotension',
             this.weightCalculation = [];
                    population: 'general',
 
                    references: [
            if( !Array.isArray( weightCalculationIds ) ) {
                        'Hollenberg SM. Vasoactive drugs in circulatory shock. Am J Respir Crit Care Med. 2011 Apr 1;183(7):847-55. doi: 10.1164/rccm.201006-0972CI. Epub 2010 Nov 19. PMID: 21097695.',
                weightCalculationIds = [ weightCalculationIds ];
                        'Bellomo R, Chapman M, Finfer S, Hickling K, Myburgh J. Low-dose dopamine in patients with early renal dysfunction: a placebo-controlled randomised trial. Australian and New Zealand Intensive Care Society (ANZICS) Clinical Trials Group. Lancet. 2000 Dec 23-30;356(9248):2139-43. doi: 10.1016/s0140-6736(00)03495-4. PMID: 11191541.'
            }
                    ],
 
                    dose: {
            for( var iWeightCalculation in weightCalculationIds ) {
                        min: '2 mcg/kg/min',
                var weightCalculationId = weightCalculationIds[ iWeightCalculation ];
                        max: '50 mcg/kg/min'
                var weightCalculation = mw.calculators.getCalculation( weightCalculationId );
                    }
 
                 }, {
                 if( !weightCalculation ) {
                     indication: 'hypotension',
                     throw new Error( 'Drug dose references weight calculation "' + weightCalculationId + '" which is not defined' );
                    population: 'pediatric',
                    dose: {
                        min: '5 mcg/kg/min',
                        max: '20 mcg/kg/min'
                    }
                 }
                 }
             ],
 
             preparations: [
                this.weightCalculation.push( weightCalculation );
                 {
            }
                    concentration: '1.6 mg/mL'
        } else {
                 }, {
            this.weightCalculation = [];
                     concentration: '40 mg/mL'
        }
 
        var mathProperties = this.getMathProperties();
        var isWeightDependent = false;
 
        for( var iMathProperty in mathProperties ) {
             var mathProperty = mathProperties[ iMathProperty ];
 
             if( this[ mathProperty ] ) {
                 // TODO consider making a UnitsBase.weight.fromString()
                this[ mathProperty ] = this[ mathProperty ].replace( 'kg', 'kgwt' );
                this[ mathProperty ] = this[ mathProperty ].replace( 'mcg', 'ug' );
 
                this[ mathProperty ] = math.unit( this[ mathProperty ] );
 
                 if( mw.calculators.isValueDependent( this[ mathProperty ], 'weight' ) ) {
                     isWeightDependent = true;
                 }
                 }
             ]
             } else {
                this[ mathProperty ] = null;
            }
        }
 
        if( isWeightDependent ) {
            // Default is tbw
            this.weightCalculation.push( mw.calculators.getCalculation( 'tbw' ) );
        }
    };
 
    mw.calculators.objectClasses.DrugDose.prototype = Object.create( mw.calculators.objectClasses.CalculatorObject.prototype );
 
    mw.calculators.objectClasses.DrugDose.prototype.getAdministration = function() {
        var administration = '';
 
        if( this.frequency ) {
            administration += administration ? ' ' : '';
            administration += this.frequency;
        }
 
        if( this.duration ) {
            administration += administration ? ' ' : '';
            administration += 'over ' + this.duration;
        }
 
        return administration;
    };
 
    mw.calculators.objectClasses.DrugDose.prototype.getCalculationData = function() {
        var calculationData = new mw.calculators.objectClasses.CalculationData();
 
        for( var iWeightCalculation in this.weightCalculation ) {
            calculationData.calculations.optional.push( this.weightCalculation[ iWeightCalculation ].id );
         }
         }
    } );


        return calculationData;
    };


    mw.calculators.objectClasses.DrugDose.prototype.getMathProperties = function() {
        return [
            'dose',
            'min',
            'max',
            'absoluteMin',
            'absoluteMax'
        ];
    };


     /**
     mw.calculators.objectClasses.DrugDose.prototype.getProperties = function() {
    * Ephedrine
         return {
    */
             required: [
    mw.calculators.addDrugs( {
                 'id'
         ephedrine: {
             name: 'Ephedrine',
            color: 'cardiovascularAgonist',
            dosages: [
                 {
                    indication: 'hypotension',
                    population: 'general',
                    dose: {
                        min: '2.5 mg',
                        max: '25 mg'
                    }
                }, {
                    indication: 'hypotension',
                    population: 'general',
                    route: 'im',
                    dose: {
                        min: '25 mg',
                        max: '50 mg'
                    }
                }, {
                    indication: 'hypotension',
                    population: 'pediatric',
                    dose: {
                        min: '0.02 mg/kg',
                        max: '0.2 mg/kg'
                    }
                }
             ],
             ],
             preparations: [
             optional: [
                 {
                 'absoluteMax',
                    concentration: '5 mg/mL'
                'absoluteMin',
                 }, {
                'dose',
                    concentration: '50 mg/mL'
                'duration',
                 }
                'frequency',
                'min',
                 'max',
                'name',
                 'weightCalculation'
             ]
             ]
         }
         };
     } );
     };
 
 




    mw.calculators.getDrugDosageCalculationId = function( drugId ) {
        return 'drugDosages-' + drugId;
    };


     /**
     /**
     * Epinephrine
     * Class DrugDosageCalculation
    * @param {Object} propertyValues
    * @returns {mw.calculators.objectClasses.DrugDosageCalculation}
    * @constructor
     */
     */
     mw.calculators.addDrugs( {
     mw.calculators.objectClasses.DrugDosageCalculation = function( propertyValues ) {
         epinephrine: {
        mw.calculators.objectClasses.CalculatorObject.call( this, this.getProperties(), propertyValues );
             name: 'Epinephrine',
 
             color: 'epinephrine',
        this.initialize();
             description: 'First line vasopressor for anaphylactic shock',
    };
             dosages: [
 
                {
    mw.calculators.objectClasses.DrugDosageCalculation.prototype = Object.create( mw.calculators.objectClasses.AbstractCalculation.prototype );
                    indication: 'hypotension',
 
                    population: 'general',
    mw.calculators.objectClasses.DrugDosageCalculation.prototype.calculate = function( data ) {
                    references: [
         var value = {
                        'Hollenberg SM. Vasoactive drugs in circulatory shock. Am J Respir Crit Care Med. 2011 Apr 1;183(7):847-55. doi: 10.1164/rccm.201006-0972CI. Epub 2010 Nov 19. PMID: 21097695.'
             message: null,
                     ],
             population: null,
                     dose: [
             preparation: data.preparation,
                        {
             dose: []
                            name: 'Bolus',
        };
                            min: '5 mcg',
 
                            max: '20 mcg',
        this.activeDosageId = null;
                            frequency: 'q1-5m'
 
                         }, {
        if( !data.drug.dosages.length ) {
                             name: 'Infusion',
            value.message = 'No dose data';
                            min: '0.01 mcg/kg/min',
 
                             max: '2 mcg/kg/min'
            return value;
        }
 
        // Determine which dosage to use
        var populationScores = [];
 
        for( var iDosage in data.drug.dosages ) {
            var drugDosage = data.drug.dosages[ iDosage ];
 
            // If the indication and route do not match, set the score to -1
            var populationScore =
                drugDosage.indication.id === data.indication.id && drugDosage.route.id === data.route.id ?
                drugDosage.population.getCalculationDataScore( data ) : -1;
 
            populationScores.push( populationScore );
        }
 
        var maxPopulationScore = Math.max.apply( null, populationScores );
 
        if( maxPopulationScore < 0 ) {
            value.message = 'No dose data for indication "' + String( data.indication ) + '" and route "' + String( data.route ) + '"';
 
            return value;
        }
 
        // If there is more than one dosage with the same score, take the first.
        // This allows the data editor to decide which is most important.
        this.activeDosageId = populationScores.indexOf( maxPopulationScore );
 
        var dosage = data.drug.dosages[ this.activeDosageId ];
 
        // A dosage may contain multiple doses (e.g. induction and maintenance)
        for( var iDose in dosage.dose ) {
            var dose = dosage.dose[ iDose ];
            var mathProperties = dose.getMathProperties();
 
            var weightCalculation = null;
            var weightValue = null;
 
            // data.weightCalculation should be in order of preference, so take the first non-null value
            for( var iWeightCalculation in dose.weightCalculation ) {
                if( dose.weightCalculation[ iWeightCalculation ].value !== null ) {
                     weightCalculation = dose.weightCalculation[ iWeightCalculation ];
                     weightValue = dose.weightCalculation[ iWeightCalculation ].value;
 
                    break;
                }
            }
 
            // Initialize value properties for dose
            value.dose[ iDose ] = {
                massPerWeight: {},
                mass: {},
                volume: {},
                weightCalculation: weightCalculation ? weightCalculation : null
            };
 
            var massUnits;
            var volumeUnits;
 
            for( var iMathProperty in mathProperties ) {
                var mathProperty = mathProperties[ iMathProperty ];
 
                var doseValue = dose[ mathProperty ];
 
                if( doseValue ) {
                    var doseUnitsByBase = mw.calculators.getUnitsByBase( doseValue );
 
                    if( doseUnitsByBase.hasOwnProperty( 'weight' ) ) {
                         value.dose[ iDose ].massPerWeight[ mathProperty ] = doseValue;
 
                        if( weightValue ) {
                             massUnits = doseUnitsByBase.mass;
 
                            if( doseUnitsByBase.hasOwnProperty( 'time' ) ) {
                                massUnits += '/' + doseUnitsByBase.time;
                            }
 
                            // For whatever reason math.format will simplify the units, but math.formatUnits will not
                             // as a hack, we recreate a new unit value with the correct formatting of the result
                            value.dose[ iDose ].mass[ mathProperty ] = math.unit( math.multiply( doseValue, weightValue ).format() ).to( massUnits );
                         }
                         }
                     ]
                     } else {
                }, {
                        value.dose[ iDose ].mass[ mathProperty ] = doseValue;
                     indication: 'hypotension',
                     }
                    population: 'pediatric',
 
                     references: [
                     if( data.preparation && value.dose[ iDose ].mass[ mathProperty ] ) {
                        'Marino BS, Tabbutt S, MacLaren G, Hazinski MF, Adatia I, Atkins DL, Checchia PA, DeCaen A, Fink EL, Hoffman GM, Jefferies JL, Kleinman M, Krawczeski CD, Licht DJ, Macrae D, Ravishankar C, Samson RA, Thiagarajan RR, Toms R, Tweddell J, Laussen PC; American Heart Association Congenital Cardiac Defects Committee of the Council on Cardiovascular Disease in the Young; Council on Clinical Cardiology; Council on Cardiovascular and Stroke Nursing; Council on Cardiovascular Surgery and Anesthesia; and Emergency Cardiovascular Care Committee. Cardiopulmonary Resuscitation in Infants and Children With Cardiac Disease: A Scientific Statement From the American Heart Association. Circulation. 2018 May 29;137(22):e691-e782. doi: 10.1161/CIR.0000000000000524. Epub 2018 Apr 23. PMID: 29685887.',
                        // Need a special case for pct
                         'Reiter PD, Roth J, Wathen B, LaVelle J, Ridall LA. Low-Dose Epinephrine Boluses for Acute Hypotension in the PICU. Pediatr Crit Care Med. 2018 Apr;19(4):281-286. doi: 10.1097/PCC.0000000000001448. PMID: 29319635.'
                         if( data.preparation.concentration.formatUnits() === 'pct' ) {
                    ],
                             volumeUnits = 'mL';
                    dose: [
                         } else {
                        {
                             var preparationUnitsByBase = mw.calculators.getUnitsByBase( data.preparation.concentration );
                             name: 'Bolus',
 
                            min: '1 mcg/kg',
                             volumeUnits = preparationUnitsByBase.volume;
                            max: '5 mcg/kg',
                            frequency: 'q1-5m'
                         }, {
                             name: 'Infusion',
                             min: '0.02 mcg/kg/min',
                            max: '1 mcg/kg/min'
                         }
                         }
                    ]
 
                }, {
                         if( doseUnitsByBase.hasOwnProperty( 'time' ) ) {
                    indication: 'acls',
                             volumeUnits += '/' + doseUnitsByBase.time;
                    population: 'general',
                    dose: {
                         dose: '1 mg',
                        frequency: 'q3-5m'
                    }
                }, {
                    indication: 'acls',
                    population: 'pediatric',
                    dose: {
                        dose: '10 mcg/kg',
                        absoluteMax: '1 mg',
                        frequency: 'q3-5m'
                    }
                }, {
                    indication: 'anaphylaxis',
                    population: 'general',
                    dose: [
                        {
                             name: 'Bolus',
                            dose: '10 mcg/kg',
                            absoluteMax: '0.5 mg',
                            frequency: 'q3-5m'
                        }, {
                            name: 'Infusion',
                            min: '0.1 mcg/kg/min',
                            max: '1 mcg/kg/min'
                         }
                         }
                    ]
 
                }, {
                        // Same hack as above to get units to simplify correctly
                    indication: 'anaphylaxis',
                        value.dose[ iDose ].volume[ mathProperty ] = math.unit( math.multiply( value.dose[ iDose ].mass[ mathProperty ], math.divide( 1, data.preparation.concentration ) ).format() ).to( volumeUnits );
                    population: 'general',
                    route: 'im',
                    dose: {
                        dose: '10 mcg/kg',
                        frequency: 'q5-15m',
                        absoluteMax: '0.5 mg'
                     }
                     }
                 }
                 }
             ],
             }
             preparations: [
 
                {
             if( value.dose[ iDose ].mass.hasOwnProperty( 'absoluteMin' ) ) {
                    concentration: '10 mcg/mL'
                 if( value.dose[ iDose ].mass.hasOwnProperty( 'max' ) && math.larger( value.dose[ iDose ].mass.absoluteMin, value.dose[ iDose ].mass.max ) ) {
                 }, {
                     // Both min and max are larger than the absolute max dose, so just convert to single dose.
                    concentration: '16 mcg/mL'
                     value.dose[ iDose ].mass.dose = value.dose[ iDose ].mass.absoluteMin;
                }, {
 
                     concentration: '100 mcg/mL',
                     delete value.dose[ iDose ].mass.min;
                     default: true
                    delete value.dose[ iDose ].mass.max;
                }, {
                     concentration: '1 mg/mL'
                }
            ]
        }
    } );


                    if( value.dose[ iDose ].volume.hasOwnProperty( 'absoluteMin' ) ) {
                        value.dose[ iDose ].volume.dose = value.dose[ iDose ].volume.absoluteMin;


                        delete value.dose[ iDose ].volume.min;
                        delete value.dose[ iDose ].volume.max;
                    }
                } else if( value.dose[ iDose ].mass.hasOwnProperty( 'min' ) && math.larger( value.dose[ iDose ].mass.absoluteMin, value.dose[ iDose ].mass.min ) ) {
                    value.dose[ iDose ].mass.min = value.dose[ iDose ].mass.absoluteMin;


    /**
                    if( value.dose[ iDose ].volume.hasOwnProperty( 'absoluteMin' ) ) {
    * Etomidate
                        value.dose[ iDose ].volume.min = value.dose[ iDose ].volume.absoluteMin;
    */
                    }
    mw.calculators.addDrugs( {
                } else if( value.dose[ iDose ].mass.hasOwnProperty( 'dose' ) && math.larger( value.dose[ iDose ].mass.absoluteMin, value.dose[ iDose ].mass.dose ) ) {
        etomidate: {
                     value.dose[ iDose ].mass.dose = value.dose[ iDose ].mass.absoluteMin;
            name: 'Etomidate',
 
            color: 'sedativeHypnotic',
                     if( value.dose[ iDose ].volume.hasOwnProperty( 'absoluteMin' ) ) {
            dosages: [
                         value.dose[ iDose ].volume.dose = value.dose[ iDose ].volume.absoluteMin;
                {
                     indication: 'generalAnesthesia',
                     population: 'general',
                    dose: {
                         min: '0.2 mg/kg',
                        max: '0.6 mg/kg',
                        weightCalculation: [ 'lbw', 'ibw' ]
                     }
                     }
                 }
                 }
             ],
             }
             preparations: [
 
                 {
             if( value.dose[ iDose ].mass.hasOwnProperty( 'absoluteMax' ) ) {
                     concentration: '2 mg/mL'
                 if( value.dose[ iDose ].mass.hasOwnProperty( 'min' ) && math.smaller( value.dose[ iDose ].mass.absoluteMax, value.dose[ iDose ].mass.min ) ) {
                }
                     // Both min and max are larger than the absolute max dose, so just convert to single dose.
            ]
                    value.dose[ iDose ].mass.dose = value.dose[ iDose ].mass.absoluteMax;
        }
 
    } );
                    delete value.dose[ iDose ].mass.min;
                    delete value.dose[ iDose ].mass.max;


                    if( value.dose[ iDose ].volume.hasOwnProperty( 'absoluteMax' ) ) {
                        value.dose[ iDose ].volume.dose = value.dose[ iDose ].volume.absoluteMax;


                        delete value.dose[ iDose ].volume.min;
                        delete value.dose[ iDose ].volume.max;
                    }
                } else if( value.dose[ iDose ].mass.hasOwnProperty( 'max' ) && math.smaller( value.dose[ iDose ].mass.absoluteMax, value.dose[ iDose ].mass.max ) ) {
                    value.dose[ iDose ].mass.max = value.dose[ iDose ].mass.absoluteMax;


    /**
                    if( value.dose[ iDose ].volume.hasOwnProperty( 'absoluteMax' ) ) {
    * Ketamine
                        value.dose[ iDose ].volume.max = value.dose[ iDose ].volume.absoluteMax;
    */
    mw.calculators.addDrugs( {
        ketamine: {
            name: 'Ketamine',
            color: 'sedativeHypnotic',
            dosages: [
                {
                    indication: 'generalAnesthesia',
                    population: 'general',
                    dose: {
                        min: '1 mg/kg',
                        max: '2 mg/kg',
                        weightCalculation: [ 'lbw', 'ibw'  ]
                     }
                     }
                 }, {
                 } else if( value.dose[ iDose ].mass.hasOwnProperty( 'dose' ) && math.smaller( value.dose[ iDose ].mass.absoluteMax, value.dose[ iDose ].mass.dose ) ) {
                    indication: 'generalAnesthesia',
                     value.dose[ iDose ].mass.dose = value.dose[ iDose ].mass.absoluteMax;
                     population: 'general',
 
                    route: 'im',
                     if( value.dose[ iDose ].volume.hasOwnProperty( 'absoluteMax' ) ) {
                     dose: {
                         value.dose[ iDose ].volume.dose = value.dose[ iDose ].volume.absoluteMax;
                        min: '4 mg/kg',
                         max: '6 mg/kg'
                     }
                     }
                }, {
                    indication: 'analgesia',
                    population: 'general',
                    description: 'To prevent prolonged recovery, do not administer in last hour of surgery.',
                    references: [
                        'Sabine Himmelseher, Marcel E. Durieux, Richard B. Weiskopf; Ketamine for Perioperative Pain Management. Anesthesiology 2005; 102:211–220 doi: https://doi.org/10.1097/00000542-200501000-00030'
                    ],
                    dose: [
                        {
                            name: 'Load (before incision)',
                            min: '0.25 mg/kg',
                            max: '0.5 mg/kg'
                        }, {
                            name: 'Maintenance',
                            min: '0.125 mg/kg',
                            max: '0.25 mg/kg',
                            frequency: 'q30m'
                        }
                    ]
                 }
                 }
             ],
             }
            preparations: [
        }
                {
 
                    concentration: '10 mg/mL'
        return value;
                }, {
    };
                    concentration: '50 mg/mL'
 
                }, {
    mw.calculators.objectClasses.DrugDosageCalculation.prototype.doRender = function() {
                    concentration: '100 mg/mL'
        var $calculationContainer = $( '.' + this.getContainerId() );
                }
 
             ]
        if( !$calculationContainer.length ) {
             return;
         }
         }
    } );


        // Add all required classes
        $calculationContainer.addClass( 'border ' + this.getContainerClasses() );
        // Add search phrases
        $calculationContainer.attr( 'data-search', this.getSearchString() );
        // Store this object in a local variable since .each() will reassign this to the DOM object of each
        // calculation container.
        var calculation = this;
        var calculationCount = 0;
        // Eventually may implement different rendering, so we should regenerate
        // all elements with each iteration of the loop.
        // I.e. might show result in table and inline in 2 different places of article.
        $calculationContainer.each( function() {
            // Initalize the variables for all the elements of the calculation. These need to be in order of placement
            // in the calculation container
            var elementTypes = [
                'title',
                'dosage',
                'info'
            ];
            var elements = {};
            for( var iElementType in elementTypes ) {
                var elementType = elementTypes[ iElementType ];


                // If an input contained by $container has user input focus, $container will not rerender (would be
                // annoying behavior to the user). However, if it contains subelements which should try to rerender,
                // add those elements to the contains property.
                elements[ elementType ] = {
                    $container: null,
                    contains: [],
                    id: calculation.getContainerId() + '-' + elementType
                };


    /**
                if( calculationCount ) {
    * Ketorolac
                     elements[ elementType ].id += '-' + calculationCount;
    */
    mw.calculators.addDrugs( [
        {
            id: 'ketorolac',
            name: 'Ketorolac',
            dosages: [
                {
                    indication: 'analgesia',
                    population: 'general',
                    description: 'A 10 mg dose of ketorolac is as effective for acute pain control as a 15 or 30 mg dose<sup>1</sup>',
                     references: [
                        'Motov S, Yasavolian M, Likourezos A, Pushkar I, Hossain R, Drapkin J, Cohen V, Filk N, Smith A, Huang F, Rockoff B, Homel P, Fromm C. Comparison of Intravenous Ketorolac at Three Single-Dose Regimens for Treating Acute Pain in the Emergency Department: A Randomized Controlled Trial. Ann Emerg Med. 2017 Aug;70(2):177-184. doi: 10.1016/j.annemergmed.2016.10.014. Epub 2016 Dec 16. PMID: 27993418.'
                    ],
                    dose: {
                        min: '10 mg',
                        max: '30 mg'
                    }
                }, {
                    indication: 'analgesia',
                    population: 'pediatric',
                    dose: {
                        min: '0.5 mg/kg',
                        max: '1 mg/kg',
                        absoluteMax: '30 mg'
                    }
                 }
                 }
             ],
             }
             preparations: [
 
                 {
            // Create title element and append to container
                    concentration: '15 mg/mL'
            elements.title.$container = $( '<div>', {
                 }, {
                id: elements.title.id,
                    concentration: '30 mg/mL',
                class: 'col-12 border-bottom ' + calculation.getElementClasses( 'title' )
                     default: true
            } );
 
             elements.title.$container.append( calculation.getTitleHtml() );
 
            if( calculation.hasInfo() ) {
                // Id of the info container should already be set by getInfo()
                 elements.info.$container = calculation.getInfo();
            }
 
            // Create the dosage element
            elements.dosage.$container = $( '<div>', {
                id: elements.dosage.id,
                class: 'row no-gutters ' + calculation.getElementClasses( 'dosage' )
            } );
 
            // Dose column
            var $dose = $( '<div>', {
                 id: calculation.getContainerId() + '-dose',
                class: 'col-7 ' + calculation.getElementClasses( 'dose' )
            }  );
 
            var dash = '-';
 
            // The options column should only show the preparation if there is a calculated volume
            var hasVolume;
 
            if( !calculation.value || calculation.activeDosageId === null ) {
                if( calculation.value && calculation.value.hasOwnProperty( 'message' ) ) {
                     $dose.append( $( '<i>' ).append( calculation.value.message ) );
                 }
                 }
             ]
             } else {
        }
                var dosage = calculation.drug.dosages[ calculation.activeDosageId ];
    ] );


                if( dosage.population && dosage.population.id !== DEFAULT_DRUG_POPULATION ) {
                    var $dosePopulation = $( '<div>', {
                        class: calculation.getElementClasses( 'dose-info' )
                    } );


                    $dosePopulation
                        .append( $( '<div>', {
                            class: calculation.getElementClasses( 'dose-info-population' )
                        } ).append( String( dosage.population ) + ' dosing' ) );


    /**
                    $dose.append( $dosePopulation );
    * Lidocaine
    */
    mw.calculators.addDrugs( {
        lidocaine: {
            name: 'Lidocaine',
            color: 'localAnesthetic',
            dosages: [
                {
                    indication: 'intubation',
                    population: 'general',
                    dose: {
                        dose: '1 mg/kg'
                    }
                 }
                 }
            ],
            preparations: [
                {
                    concentration: '1 pct'
                }, {
                    concentration: '2 pct'
                }
            ]
        }
    } );


                var $doseData = $( '<div>', {
                    class: calculation.getElementClasses( 'dose-data' )
                } );


                // This will iterate through the calculated doses. iDose should exactly correspond to doses within dosage
                // to allow referencing other properties of the dose.
                for( var iDose in calculation.value.dose ) {
                    var dose = dosage.dose[ iDose ];
                    var doseValue = calculation.value.dose[ iDose ];


    /**
                    if( dose.name ) {
    * Midazolam
                        $doseData.append( dose.name + '<br />' );
    */
    mw.calculators.addDrugs( {
        midazolam: {
            name: 'Midazolam',
            color: 'benzodiazepine',
            dosages: [
                {
                    indication: 'anxiolysis',
                    population: 'general',
                    dose: {
                        min: '0.01 mg/kg',
                        max: '0.03 mg/kg'
                     }
                     }
                }, {
 
                     indication: 'anxiolysis',
                     var $doseList = $( '<ul>' );
                     population: 'general',
 
                     route: 'im',
                    var administration = dose.getAdministration();
                     dose: {
                     var administrationDisplayed = false;
                         min: '0.07 mg/kg',
 
                         max: '0.08 mg/kg'
                     var massPerWeightHtml = '';
 
                     if( doseValue.massPerWeight.hasOwnProperty( 'dose' ) ) {
                         massPerWeightHtml += mw.calculators.getValueString( doseValue.massPerWeight.dose );
                    } else if( doseValue.massPerWeight.hasOwnProperty( 'min' ) &&
                        doseValue.massPerWeight.hasOwnProperty( 'max' ) ) {
 
                        // getValueString will simplify the value and may adjust the units
                        var massPerWeightMinValue = math.unit( mw.calculators.getValueString( doseValue.massPerWeight.min ) );
                         var massPerWeightMaxValue = math.unit( mw.calculators.getValueString( doseValue.massPerWeight.max ) );
 
                        if( massPerWeightMinValue.formatUnits() !== massPerWeightMaxValue.formatUnits() ) {
                            // If the units between min and max don't match, show both
                            massPerWeightHtml += mw.calculators.getValueString( massPerWeightMinValue );
                        } else {
                            massPerWeightHtml += mw.calculators.getValueNumber( massPerWeightMinValue );
                        }
 
                        massPerWeightHtml += dash;
                        massPerWeightHtml += mw.calculators.getValueString( massPerWeightMaxValue );
                     }
                     }
                }, {
 
                    indication: 'anxiolysis',
                    if( massPerWeightHtml ) {
                    population: 'general',
                        if( administration && ! administrationDisplayed ) {
                    route: 'po',
                            massPerWeightHtml += ' ' + administration;
                    dose: {
                            administrationDisplayed = true;
                         dose: '0.5 mg/kg',
                        }
                         absoluteMax: '20 mg'
 
                        var massPerWeightNotesHtml = '';
 
                        if( doseValue.mass.hasOwnProperty( 'absoluteMin' ) ) {
                            massPerWeightNotesHtml += 'Min: ' + mw.calculators.getValueString( doseValue.mass.absoluteMin );
                        } else if( doseValue.mass.hasOwnProperty( 'absoluteMax' ) ) {
                            massPerWeightNotesHtml += 'Max: ' + mw.calculators.getValueString( doseValue.mass.absoluteMax );
                        }
 
                         if( dose.weightCalculation && dose.weightCalculation[ 0 ].id !== 'tbw' ) {
                            if( massPerWeightNotesHtml ) {
                                massPerWeightNotesHtml += ', ';
                            }
 
                            massPerWeightNotesHtml += dose.weightCalculation[ 0 ].getTitleString();
                        }
 
                        if( massPerWeightNotesHtml ) {
                            massPerWeightHtml += ' (' + massPerWeightNotesHtml + ')';
                        }
 
                         massPerWeightHtml = $( '<li>' ).append( massPerWeightHtml );
 
                        $doseList.append( massPerWeightHtml );
                     }
                     }
                }, {
 
                     indication: 'generalAnesthesia',
                     var massHtml = '';
                     population: 'general',
 
                    dose: {
                     if( doseValue.mass.hasOwnProperty( 'dose' ) ) {
                        min: '0.1 mg/kg',
                        massHtml += mw.calculators.getValueString( doseValue.mass.dose );
                         max: '0.3 mg/kg',
                    } else if( doseValue.mass.hasOwnProperty( 'min' ) &&
                         weightCalculation: [ 'lbw', 'ibw' ]
                        doseValue.mass.hasOwnProperty( 'max' ) ) {
 
                        // getValueString will simplify the value and may adjust the units
                        var massMinValue = math.unit( mw.calculators.getValueString( doseValue.mass.min ) );
                         var massMaxValue = math.unit( mw.calculators.getValueString( doseValue.mass.max ) );
 
                        if( massMinValue.formatUnits() !== massMaxValue.formatUnits() ) {
                            // If the units between min and max don't match, show both
                            massHtml += mw.calculators.getValueString( massMinValue );
                         } else {
                            massHtml += mw.calculators.getValueNumber( massMinValue );
                        }
 
                        massHtml += dash;
                        massHtml += mw.calculators.getValueString( massMaxValue );
                     }
                     }
                }
            ],
            preparations: [
                {
                    concentration: '1 mg/mL'
                }, {
                    concentration: '5 mg/mL'
                }
            ]
        }
    } );


                    if( massHtml ) {
                        if( administration && ! administrationDisplayed ) {
                            massHtml += ' ' + administration;
                            administrationDisplayed = true;
                        }
                        if( dose.weightCalculation.length && doseValue.weightCalculation.id !== dose.weightCalculation[ 0 ].id ) {
                            var weightCalculationLabel = doseValue.weightCalculation.getTitleString();


                            var $weightCalculationInfoIcon = $( '<i>', {
                                class: 'far fa-question-circle',
                                'data-toggle': 'popover',
                                'data-trigger': 'focus',
                                'data-content': String( doseValue.weightCalculation ) +
                                    ' is being used because data is missing for ' +
                                    String( dose.weightCalculation[ 0 ] ) + ': ' + dose.weightCalculation[ 0 ].message
                            } );


    /**
                            massHtml += '&nbsp; (' + weightCalculationLabel + '&nbsp;' + $weightCalculationInfoIcon[ 0 ].outerHTML + ')';
    * Milrinone
    */
    mw.calculators.addDrugs( {
        milrinone: {
            name: 'Milrinone',
            color: 'cardiovascularAgonist',
            description: '<ul><li>Used in cardiogenic shock</li><li>Dose adjustment needed in renal impairment</li></ul>',
            dosages: [
                {
                    indication: 'hypotension',
                    population: 'general',
                    references: [
                        'Hollenberg SM. Vasoactive drugs in circulatory shock. Am J Respir Crit Care Med. 2011 Apr 1;183(7):847-55. doi: 10.1164/rccm.201006-0972CI. Epub 2010 Nov 19. PMID: 21097695.'
                    ],
                    dose: [
                        {
                            name: 'Load (optional)',
                            dose: '50 mcg/kg',
                            duration: '10 min'
                        }, {
                            name: 'Infusion',
                            min: '0.125 mcg/kg/min',
                            max: '0.75 mcg/kg/min'
                         }
                         }
                    ]
                }
            ],
            preparations: [
                {
                    concentration: '0.2 mg/mL'
                }, {
                    concentration: '1 mg/mL'
                }
            ]
        }
    } );


                        massHtml = $( '<li>' ).append( massHtml );


                        $doseList.append( massHtml );
                    }


    /**
                     var volumeHtml = '';
    * Neostigmine
    */
    mw.calculators.addDrugs( {
        neostigmine: {
            name: 'Neostigmine',
            color: 'neuromuscularBlockerReversal',
            dosages: [
                {
                    indication: 'neuromuscularBlockadeReversal',
                    population: 'general',
                    description: 'For each 1 mg of neostigmine, give 0.2 mg of glycopyrrolate to avoid bradycardia',
                    dose: {
                        min: '0.03 mg/kg',
                        max: '0.07 mg/kg',
                        absoluteMax: '5 mg'
                    }
                }
            ],
            preparations: [
                {
                    concentration: '0.5 mg/mL'
                }, {
                     concentration: '1 mg/mL'
                }
            ]
        }
    } );


                    if( doseValue.volume.hasOwnProperty( 'dose' ) ) {
                        volumeHtml += mw.calculators.getValueString( doseValue.volume.dose );
                    } else if( doseValue.volume.hasOwnProperty( 'min' ) &&
                        doseValue.volume.hasOwnProperty( 'max' ) ) {


                        // getValueString will simplify the value and may adjust the units
                        var volumeMinValue = math.unit( mw.calculators.getValueString( doseValue.volume.min ) );
                        var volumeMaxValue = math.unit( mw.calculators.getValueString( doseValue.volume.max ) );


    /**
                        if( volumeMinValue.formatUnits() !== volumeMaxValue.formatUnits() ) {
    * Norepinephrine
                            // If the units between min and max don't match, show both
    */
                            volumeHtml += mw.calculators.getValueString( volumeMinValue );
    mw.calculators.addDrugs( {
                         } else {
        norepinephrine: {
                             volumeHtml += mw.calculators.getValueNumber( volumeMinValue );
            name: 'Norepinephrine',
            color: 'cardiovascularAgonist',
            description: 'First line vasopressor for septic and hypovolemic shock',
            dosages: [
                {
                    indication: 'hypotension',
                    population: 'general',
                    references: [
                        'Hollenberg SM. Vasoactive drugs in circulatory shock. Am J Respir Crit Care Med. 2011 Apr 1;183(7):847-55. doi: 10.1164/rccm.201006-0972CI. Epub 2010 Nov 19. PMID: 21097695.'
                    ],
                    dose: [
                        {
                            name: 'Bolus',
                            min: '5 mcg',
                            max: '20 mcg'
                         }, {
                             name: 'Infusion',
                            min: '0.05 mcg/kg/min',
                            max: '3.3 mcg/kg/min'
                         }
                         }
                    ]
 
                }, {
                        volumeHtml += dash;
                     indication: 'hypotension',
                        volumeHtml += mw.calculators.getValueString( doseValue.volume.max );
                    population: 'pediatric',
                     }
                     dose: [
 
                        {
                     if( volumeHtml ) {
                            name: 'Bolus',
                         if( administration && ! administrationDisplayed ) {
                            min: '0.05 mcg/kg',
                             volumeHtml += ' ' + administration;
                            max: '0.1 mcg/kg'
                             administrationDisplayed = true;
                         }, {
                             name: 'Infusion',
                            min: '0.05 mcg/kg/min',
                             max: '2 mcg/kg/min'
                         }
                         }
                    ]
                }
            ],
            preparations: [
                {
                    concentration: '10 mcg/mL'
                }, {
                    concentration: '16 mcg/mL'
                }, {
                    concentration: '1 mg/mL',
                    dilutionRequired: true
                }
            ]
        }
    } );


                        volumeHtml = $( '<li>' ).append( volumeHtml );


                        $doseList.append( volumeHtml );


    /**
                         hasVolume = true;
    * Ondansetron
    */
    mw.calculators.addDrugs( {
        ondansetron: {
            name: 'Ondansetron',
            dosages: [
                {
                    indication: 'ponv',
                    population: 'general',
                    dose: {
                         dose: '4 mg'
                    }
                }, {
                    indication: 'ponv',
                    population: 'pediatric',
                    dose: {
                        dose: '0.1 mg/kg',
                        absoluteMax: '4 mg'
                     }
                     }
                    $doseData.append( $doseList );
                 }
                 }
            ],
 
            preparations: [
                $dose.append( $doseData );
                 {
 
                     concentration: '2 mg/mL'
                 if( calculation.hasInfo() ) {
                     $dose.append( calculation.getInfoButton( calculationCount ) );
                 }
                 }
             ]
             }
        }
 
    } );
 
            // Options column
            var $options = $( '<div>', {
                id: calculation.getContainerId() + '-options',
                class: 'col-5 ' + calculation.getElementClasses( 'options' )
            } );
 
            var optionsRowClass = 'row no-gutters align-items-center';
 
            if( !mw.calculators.isMobile() ) {
                optionsRowClass += ' mb-2';
            }
 
            var optionLabelClass = mw.calculators.isMobile() ? 'col-4' : 'col-3';
            var optionValueClass = mw.calculators.isMobile() ? 'col-8' : 'col-9';
 
            var indications = calculation.drug.getIndications();
 
            if( indications.length ) {
                var indicationVariable = mw.calculators.getVariable( calculation.getVariableIds().indication );
 
                $options
                    .append( $( '<div>', {
                        class: optionsRowClass
                    } )
                        .append(
                            $( '<div>', {
                                class: optionLabelClass,
                                html: indicationVariable.getLabelString() + '&nbsp;'
                            } ),
                            $( '<div>', {
                                class: optionValueClass
                            } )
                                .append( indicationVariable.createInput({
                                    class: 'calculator-container-input-DrugDosageCalculator-options',
                                    hideLabel: true,
                                    inline: true
                                } ) ) ) );
            }
 
            var routes = calculation.drug.getRoutes();
 
            if( routes.length ) {
                var routeVariable = mw.calculators.getVariable( calculation.getVariableIds().route );
 
                $options
                    .append( $( '<div>', {
                        class: optionsRowClass
                    } )
                        .append(
                            $( '<div>', {
                                class: optionLabelClass,
                                html: routeVariable.getLabelString() + '&nbsp;'
                            } ),
                            $( '<div>', {
                                class: optionValueClass
                            } )
                                .append( routeVariable.createInput({
                                    class: 'calculator-container-input-DrugDosageCalculator-options',
                                    hideLabel: true,
                                    inline: true
                                } ) ) ) );
            }


            // Don't show preparations if there isn't a dose with volume
            if( hasVolume ) {
                var preparations = calculation.drug.getPreparations();


                if( preparations.length ) {
                    var preparationVariable = mw.calculators.getVariable( calculation.getVariableIds().preparation );


    /**
                    $options
    * Phenylephrine
                        .append( $( '<div>', {
    */
                            class: optionsRowClass
    mw.calculators.addDrugs( {
                         } )
        phenylephrine: {
                             .append(
            name: 'Phenylephrine',
                                $( '<div>', {
            color: 'cardiovascularAgonist',
                                    class: optionLabelClass,
            dosages: [
                                    html: preparationVariable.getLabelString() + '&nbsp;'
                {
                                } ),
                    indication: 'hypotension',
                                $( '<div>', {
                    population: 'general',
                                    class: optionValueClass
                    dose: [
                                } )
                         {
                                    .append( preparationVariable.createInput({
                             name: 'Bolus',
                                        class: 'calculator-container-input-DrugDosageCalculator-options',
                            min: '50 mcg',
                                        hideLabel: true,
                            max: '200 mcg'
                                        inline: true
                        }, {
                                    } ) ) ) );
                            name: 'Infusion',
                            min: '0.25 mcg/kg/min',
                            max: '1 mcg/kg/min'
                        }
                    ]
                }, {
                    indication: 'hypotension',
                    population: 'pediatric',
                    dose: [
                        {
                            name: 'Bolus',
                            min: '0.5 mcg/kg',
                            max: '1 mcg/kg'
                        }, {
                            name: 'Infusion',
                            min: '0.1 mcg/kg/min',
                            max: '0.5 mcg/kg/min'
                        }
                    ]
                 }
                 }
             ],
             }
             preparations: [
 
                {
             elements.dosage.$container.append( $dose, $options );
                    concentration: '10 mcg/mL'
 
                }, {
            // Add elements to the contains array
                    concentration: '100 mcg/mL',
            elements.dosage.contains.push( $dose, $options );
                    default: true
 
                }, {
            // Iterate over elementTypes since it is in order of rendering
                    concentration: '160 mcg/mL'
            for( var iElementType in elementTypes ) {
                 }, {
                 var elementType = elementTypes[ iElementType ];
                    concentration: '10 mg/mL',
                 var element = elements[ elementType ];
                    dilutionRequired: true
 
                 }
                var $existingContainer = $( '#' + element.id );
            ]
        }
    } );


                if( $existingContainer.length ) {
                    // If an input within this container has focus (i.e. the user changed a variable input which
                    // triggered this rerender), don't rerender the element as this would destroy the focus on
                    // the input.
                    if( !$.contains( $existingContainer[ 0 ], $( ':focus' )[ 0 ] ) ) {
                        $existingContainer.replaceWith( element.$container );
                    } else {
                        for( var containedElementId in element.contains ) {
                            var $containedElement = element.contains[ containedElementId ];


                            var $existingContainedContainer = $( '#' + $containedElement.attr( 'id' ) );


    /**
                            if( $existingContainedContainer.length ) {
    * Propofol
                                if( !$.contains( $existingContainedContainer[ 0 ], $( ':focus' )[ 0 ] ) ) {
    */
                                    $existingContainedContainer.replaceWith( $containedElement );
    mw.calculators.addDrugs( {
                                }
        propofol: {
                             }
            name: 'Propofol',
            color: 'sedativeHypnotic',
            dosages: [
                {
                    indication: 'generalAnesthesia',
                    population: 'general',
                    dose: [
                        {
                            name: 'Induction',
                            min: '1 mg/kg',
                            max: '2.5 mg/kg',
                            weightCalculation: [ 'lbw', 'ibw' ]
                        }, {
                            name: 'Maintenance',
                            min: '100 mcg/kg/min',
                            max: '200 mcg/kg/min'
                        }
                    ]
                }, {
                    indication: 'generalAnesthesia',
                    population: 'pediatric',
                    dose: [
                        {
                            name: 'Induction',
                            min: '2.5 mg/kg',
                            max: '3.5 mg/kg',
                            weightCalculation: [ 'lbw', 'ibw' ]
                        }, {
                            name: 'Maintenance',
                            min: '125 mcg/kg/min',
                            max: '300 mcg/kg/min'
                        }
                    ]
                }, {
                    indication: 'generalAnesthesia',
                    population: 'geriatric',
                    dose: [
                        {
                            name: 'Induction',
                            min: '1 mg/kg',
                            max: '1.5 mg/kg',
                             weightCalculation: [ 'lbw', 'ibw' ]
                        }, {
                            name: 'Maintenance',
                            min: '50 mcg/kg/min',
                            max: '100 mcg/kg/min'
                         }
                         }
                    ]
                }, {
                    indication: 'mac',
                    population: 'general',
                    dose: {
                        min: '25 mcg/kg/min',
                        max: '75 mcg/kg/min'
                     }
                     }
                } else {
                    $( this ).append( elements[ elementType ].$container );
                 }
                 }
             ],
             }
             preparations: [
 
                 {
            calculationCount++;
                     concentration: '10 mg/mL'
        } );
 
        // Activate popovers
        $( '[data-toggle="popover"]' ).popover();
    };
 
    mw.calculators.objectClasses.DrugDosageCalculation.prototype.getCalculationData = function() {
        var inputData = new mw.calculators.objectClasses.CalculationData();
 
        // Add variables created by this calculation
        var variableIds = this.getVariableIds();
 
        for( var variableType in variableIds ) {
            inputData.variables.optional.push( variableIds[ variableType ] );
        }
 
        var dataTypes = inputData.getDataTypes();
 
        // Data is only actually required if it is required by every dosage for the drug.
        // Data marked as required by an individual dosage that does not appear in every
        // dosage will be converted to optional.
        var requiredInputData = new mw.calculators.objectClasses.CalculationData();
 
        // Need a way to tell the first iteration of the loop to initialize the required variables to a value that
        // is distinct from the empty array (populated across loop using array intersect, so could become [] and shouldn't
        // reinitialize).
        var initializeRequiredData = true;
 
        // Iterate through each dosage to determine variable dependency
        for( var iDosage in this.drug.dosages ) {
             var dosageInputData = this.drug.dosages[ iDosage ].getCalculationData();
 
            inputData = inputData.merge( dosageInputData );
 
            for( var iDataType in dataTypes ) {
                var dataType = dataTypes[ iDataType ];
 
                if( initializeRequiredData ) {
                    requiredInputData[ dataType ].required = inputData[ dataType ].required;
                 } else {
                     // Data is only truly required if it is required by all dosage calculations, so use array intersection
                    requiredInputData[ dataType ].required = requiredInputData[ dataType ].required.filter( function( index ) {
                        return dosageInputData[ dataType ].required.indexOf( index ) !== -1;
                    } );
                 }
                 }
             ]
             }
 
            initializeRequiredData = false;
        }
 
        for( var iDataType in dataTypes ) {
            var dataType = dataTypes[ iDataType ];
 
            // Move any data marked required in inputData to optional if it not actually required (i.e. doesn't appear
            // in requiredInputData).
            inputData[ dataType ].optional = inputData[ dataType ].optional.concat( inputData[ dataType ].required.filter( function( index ) {
                return requiredInputData[ dataType ].required.indexOf( index ) === -1;
            } ) ).filter( mw.calculators.uniqueValues );
 
            inputData[ dataType ].required = requiredInputData[ dataType ].required;
        }
 
        return inputData;
    };
 
    mw.calculators.objectClasses.DrugDosageCalculation.prototype.getCalculationDataValues = function() {
        var data = mw.calculators.objectClasses.AbstractCalculation.prototype.getCalculationDataValues.call( this );
 
        data.drug = this.drug;
 
        data.indication = data[ this.getVariablePrefix() + 'indication' ] !== null ?
            mw.calculators.getDrugIndication( mw.calculators.getVariable( this.getVariableIds().indication ).getValue() ) :
            null;
 
        delete data[ this.getVariablePrefix() + 'indication' ];
 
        data.preparation = data[ this.getVariablePrefix() + 'preparation' ] !== null ?
            this.drug.preparations[ mw.calculators.getVariable( this.getVariableIds().preparation ).getValue() ] :
            null;
 
        delete data[ this.getVariablePrefix() + 'preparation' ];
 
        data.route = data[ this.getVariablePrefix() + 'route' ] !== null ?
            mw.calculators.getDrugRoute( mw.calculators.getVariable( this.getVariableIds().route ).getValue() ) :
            null;
 
        delete data[ this.getVariablePrefix() + 'route' ];
 
        return data;
    };
 
    mw.calculators.objectClasses.DrugDosageCalculation.prototype.getClassName = function() {
        return 'DrugDosageCalculation';
    };
 
    mw.calculators.objectClasses.DrugDosageCalculation.prototype.getDescription = function() {
        var description = this.drug.description ? this.drug.description : '';
 
        if( this.activeDosageId !== null && this.drug.dosages[ this.activeDosageId ].description ) {
            description += description ? '<br/><br/>' : '';
            description += this.drug.dosages[ this.activeDosageId ].description;
        }
 
        return description;
    };
 
    mw.calculators.objectClasses.DrugDosageCalculation.prototype.getInfoButton = function( infoCount ) {
        var infoContainerId = this.getContainerId() + '-info';
 
        if( infoCount ) {
            infoContainerId += '-' + infoCount;
         }
         }
    } );


        var infoString = 'More information';
        infoString += !mw.calculators.isMobile() ? ' about this dose' : '';
        return $( '<div>', {
            class: this.getElementClasses( 'infoButton' )
        } )
            .append( $( '<a>', {
                'data-toggle': 'collapse',
                href: '#' + infoContainerId,
                role: 'button',
                'aria-expanded': 'false',
                'aria-controls': infoContainerId
            } )
                .append( $( '<i>', {
                    class: 'far fa-question-circle'
                } ), '&nbsp;' + infoString ) );
    };


    mw.calculators.objectClasses.DrugDosageCalculation.prototype.getProperties = function() {
        var inheritedProperties = mw.calculators.objectClasses.AbstractCalculation.prototype.getProperties();


    /**
        return this.mergeProperties( inheritedProperties, {
    * Remifentanil
             required: [
    */
                 'drug'
    mw.calculators.addDrugs( {
        remifentanil: {
             name: 'Remifentanil',
            color: 'opioid',
            dosages: [
                 {
                    indication: 'tiva',
                    population: 'general',
                    dose: [
                        {
                            name: 'Load',
                            min: '1 mcg/kg',
                            max: '2 mcg/kg',
                            weightCalculation: [ 'lbw', 'ibw' ]
                        }, {
                            name: 'Maintenance',
                            min: '0.1 mcg/kg/min',
                            max: '1 mcg/kg/min'
                        }
                    ]
                }
             ],
             ],
             preparations: [
             optional: []
                 {
        } );
                    concentration: '50 mcg/mL'
    };
                }, {
 
                    concentration: '1 mg/vial',
    mw.calculators.objectClasses.DrugDosageCalculation.prototype.getReferences = function() {
                    dilutionRequired: true
        var references = this.drug.references;
                }, {
 
                    concentration: '2 mg/vial',
        if( this.activeDosageId !== null && this.drug.dosages[ this.activeDosageId ].references.length ) {
                    dilutionRequired: true
            references = references
                }
                 .concat( this.drug.dosages[ this.activeDosageId ].references )
             ]
                .filter( mw.calculators.uniqueValues );
        }
 
        return references;
    };
 
    mw.calculators.objectClasses.DrugDosageCalculation.prototype.getSearchString = function() {
        return this.drug.name + ' ' + this.drug.color + ' ' + this.drug.getIndications().join( ' ' );
    };
 
    mw.calculators.objectClasses.DrugDosageCalculation.prototype.getTitleHtml = function() {
        var $title = $( '<a>', {
            class: this.getElementClasses( 'title-name' ),
            href: mw.util.getUrl( this.drug.name ),
            text: this.getTitleString()
        } ).css( 'background-color', '#fff' );
 
        var highlightColor = this.drug.color.getHighlightColor();
 
        if( highlightColor ) {
            var highlightContainerAttributes = {
                class: this.getElementClasses( 'title-highlight' )
            };
 
            var highlightContainerCss = {};
 
             highlightContainerCss[ 'background' ] = highlightColor;
 
            $title = $( '<span>', highlightContainerAttributes ).append( $title ).css( highlightContainerCss );
         }
         }
    } );


        var primaryColor = this.drug.color.getPrimaryColor();
        if( primaryColor ) {
            var backgroundContainerAttributes = {
                class: this.getElementClasses( 'title-background' )
            };
            var backgroundContainerCss = {};


            if( this.drug.color.isStriped() ) {
                backgroundContainerCss[ 'background' ] = 'repeating-linear-gradient(135deg,rgba(0,0,0,0),rgba(0,0,0,0)10px,rgba(255,255,255,1)10px,rgba(255,255,255,1)20px),' + primaryColor;
            } else {
                backgroundContainerCss[ 'background'] = primaryColor;
            }


    /**
            $title = $( '<span>', backgroundContainerAttributes ).append( $title ).css( backgroundContainerCss );
    * Rocuronium
    */
    mw.calculators.addDrugs( {
        rocuronium: {
            name: 'Rocuronium',
            color: 'neuromuscularBlocker',
            dosages: [
                {
                    indication: 'neuromuscularBlockade',
                    population: 'general',
                    dose: [
                        {
                            name: 'Standard',
                            dose: '0.6 mg/kg'
                        }, {
                            name: 'Rapid sequence',
                            dose: '1.2 mg/kg'
                        }
                    ]
                }
            ],
            preparations: [
                {
                    concentration: '10 mg/mL'
                }
            ]
         }
         }
    } );


        return $title;
    };
    mw.calculators.objectClasses.DrugDosageCalculation.prototype.getTitleString = function() {
        return this.drug ? this.drug.name : '';
    }


    mw.calculators.objectClasses.DrugDosageCalculation.prototype.getVariableIds = function() {
        return {
            indication: this.getVariablePrefix() + 'indication',
            preparation: this.getVariablePrefix() + 'preparation',
            route: this.getVariablePrefix() + 'route'
        };
    };


    /**
     mw.calculators.objectClasses.DrugDosageCalculation.prototype.getVariableOptions = function( variableId ) {
    * Succinylcholine
         if( variableId === this.getVariablePrefix() + 'indication' ) {
    */
            return this.drug.getIndications();
     mw.calculators.addDrugs( {
        } else if( variableId === this.getVariablePrefix() + 'preparation' ) {
         succinylcholine: {
            // Exclude preparations which require dilution
            name: 'Succinylcholine',
            return this.drug.getPreparations( true );
            color: 'succinylcholine',
        } else if( variableId === this.getVariablePrefix() + 'route' ) {
            dosages: [
             return this.drug.getRoutes();
                {
                    indication: 'neuromuscularBlockade',
                    population: 'general',
                    dose: {
                        min: '1 mg/kg',
                        max: '1.5 mg/kg'
                    }
                }, {
                    indication: 'neuromuscularBlockade',
                    population: 'general',
                    route: 'im',
                    dose: {
                        min: '3 mg/kg',
                        max: '5 mg/kg'
                    }
                }
            ],
            preparations: [
                {
                    concentration: '20 mg/mL'
                }, {
                    concentration: '100 mg/mL'
                }
             ]
         }
         }
     } );
     };


    mw.calculators.objectClasses.DrugDosageCalculation.prototype.getVariablePrefix = function() {
        return this.drug.id + '-';
    };


    mw.calculators.objectClasses.DrugDosageCalculation.prototype.initialize = function() {
        if( typeof this.drug === 'string' ) {
            var drug = mw.calculators.getDrug( this.drug );


    /**
            if( !drug ) {
    * Sufentanil
                 throw new Error( 'DrugDosage references drug "' + this.drug + '" which is not defined' );
    */
             }
    mw.calculators.addDrugs( {
 
        sufentanil: {
             this.drug = drug;
            name: 'Sufentanil',
            color: 'opioid',
            dosages: [
                 {
                    indication: 'tiva',
                    population: 'general',
                    dose: [
                        {
                            name: 'Load',
                            min: '0.25 mcg/kg',
                            max: '2 mcg/kg',
                            weightCalculation: [ 'lbw', 'ibw' ]
                        }, {
                            name: 'Maintenance',
                            min: '0.5 mcg/kg/hr',
                            max: '1.5 mcg/kg/hr'
                        }
                    ]
                }
             ],
            preparations: [
                {
                    concentration: '5 mcg/mL'
                }, {
                    concentration: '50 mcg/mL'
                }
             ]
         }
         }
    } );


        this.activeDosageId = null;
        this.updateVariables();
        mw.calculators.objectClasses.AbstractCalculation.prototype.initialize.call( this );
    };
    mw.calculators.objectClasses.DrugDosageCalculation.prototype.updateVariables = function() {
        var variableIds = this.getVariableIds();
        for( var variableType in variableIds ) {
            var variableId = variableIds[ variableType ];
            var variableOptions = this.getVariableOptions( variableId );
            var variableOptionValues = {};
            var defaultOption = 0;
            for( var iVariableOption in variableOptions ) {
                var variableOption = variableOptions[ iVariableOption ];
                defaultOption = variableOption.default ? iVariableOption : defaultOption;
                variableOptionValues[ variableOption.id ] = String( variableOption );
            }
            var defaultValue = variableOptions.length ? variableOptions[ defaultOption ].id : null;
            var variable = mw.calculators.getVariable( variableId );
            if( !variable ) {
                var newVariable = {};


                // TODO put this somewhere else
                var abbreviation;


    /**
                if( variableType === 'indication' ) {
    * Sugammadex
                    abbreviation = 'Use';
    */
                 } else if( variableType === 'route' ) {
    mw.calculators.addDrugs( {
                     abbreviation = 'Route';
        sugammadex: {
                } else if( variableType === 'preparation' ) {
            name: 'Sugammadex',
                     abbreviation = 'Prep';
            color: 'neuromuscularBlockerReversal',
            dosages: [
                 {
                    indication: 'neuromuscularBlockadeReversal',
                     population: 'general',
                    description: '<ul><li>&ge;2 twitches on TOF: 2 mg/kg</li><li>1-2 posttetanic twitches: 4 mg/kg</li><li>Immediate reversal: 16 mg/kg</li></ul>',
                     dose: {
                        min: '2 mg/kg',
                        max: '16 mg/kg'
                    }
                 }
                 }
            ],
 
            preparations: [
                newVariable[ variableId ] = {
                {
                    name: variableType.charAt(0).toUpperCase() + variableType.slice(1),
                     concentration: '100 mg/mL'
                    abbreviation: abbreviation,
                 }
                     type: 'string',
             ]
                    defaultValue: defaultValue,
                    options: variableOptionValues
                 };
 
                mw.calculators.addVariables( newVariable );
             } else {
                // Probably not ideal to reach into the variable to change these things directly
                // Perhaps add helper functions to variable class
                mw.calculators.variables[ variableId ].defaultValue = defaultValue;
                mw.calculators.variables[ variableId ].options = variableOptionValues;
            }
         }
         }
     } );
     };
 
 
 
 
 


    mw.calculators.addDrugCalculators = function( moduleId, drugCalculatorData, className ) {
        className = className ? className : 'DrugDosageCalculator';


        for( var drugCalculatorId in drugCalculatorData ) {
            drugCalculatorData[ drugCalculatorId ].module = moduleId;


    /**
            for( var iCalculation in drugCalculatorData[ drugCalculatorId].calculations ) {
    * Vasopressin
                drugCalculatorData[ drugCalculatorId].calculations[ iCalculation ] = moduleId + '-' +
    */
                     drugCalculatorData[ drugCalculatorId].calculations[ iCalculation ];
    mw.calculators.addDrugs( {
             }
        vasopressin: {
            name: 'Vasopressin',
            color: 'cardiovascularAgonist',
            dosages: [
                {
                    indication: 'hypotension',
                    population: 'general',
                    description: 'Use caution with sustained infusions >0.04 units/min which can cause cardiac and splanchnic ischemia',
                    references: [
                        'Hollenberg SM. Vasoactive drugs in circulatory shock. Am J Respir Crit Care Med. 2011 Apr 1;183(7):847-55. doi: 10.1164/rccm.201006-0972CI. Epub 2010 Nov 19. PMID: 21097695.'
                     ],
                    dose: [
                        {
                            name: 'Bolus',
                            min: '0.5 units',
                            max: '1 unit'
                        }, {
                            name: 'Infusion',
                            min: '0.01 units/min',
                            max: '0.07 units/min'
                        }
                    ]
                }, {
                    indication: 'hypotension',
                    population: 'pediatric',
                    references: [
                        'Choong K, Kissoon N. Vasopressin in pediatric shock and cardiac arrest. Pediatr Crit Care Med. 2008 Jul;9(4):372-9. doi: 10.1097/PCC.0b013e318172d7c8. PMID: 18496412.'
                    ],
                    dose: [
                        {
                            name: 'Bolus',
                            min: '0.005 units/kg',
                            max: '0.01 units/kg'
                        }, {
                            name: 'Infusion',
                            min: '0.0002 units/kg/min',
                            max: '0.008 units/kg/min'
                        }
                    ]
                }
            ],
             preparations: [
                {
                    concentration: '1 units/mL'
                }, {
                    concentration: '20 units/mL'
                }
            ]
         }
         }
     } );
 
        mw.calculators.addCalculators( moduleId, drugCalculatorData, className );
     };






     /**
     /**
     * Vecuronium
     * Class DrugDosageCalculator
    * @param {Object} propertyValues
    * @returns {mw.calculators.objectClasses.DrugDosageCalculator}
    * @constructor
     */
     */
     mw.calculators.addDrugs( {
     mw.calculators.objectClasses.DrugDosageCalculator = function( propertyValues ) {
         vecuronium: {
         mw.calculators.objectClasses.CalculatorObject.call( this, this.getProperties(), propertyValues );
            name: 'Vecuronium',
    };
            color: 'neuromuscularBlocker',
 
            dosages: [
    mw.calculators.objectClasses.DrugDosageCalculator.prototype = Object.create( mw.calculators.objectClasses.AbstractCalculator.prototype );
                {
 
                    indication: 'neuromuscularBlockade',
    mw.calculators.objectClasses.DrugDosageCalculator.prototype.getClassName = function() {
                    population: 'general',
        return 'DrugDosageCalculator';
                    dose: [
     };
                        {
 
                            name: 'Standard',
                            dose: '0.1 mg/kg'
                        }, {
                            name: 'Rapid sequence',
                            dose: '1.2 mg/kg'
                        }
                    ]
                }
            ],
            preparations: [
                {
                    concentration: '10 mg/vial'
                }
            ]
        }
     } );
}() );
}() );

Revision as of 20:13, 30 August 2021

/**
 * @author Chris Rishel
 */
( function() {
    var DEFAULT_DRUG_COLOR = 'default';
    var DEFAULT_DRUG_POPULATION = 'general';
    var DEFAULT_DRUG_ROUTE = 'iv';

    mw.calculators.isValueDependent = function( value, variableId ) {
        // This may need generalized to support other variables in the future
        if( variableId === 'weight' ) {
            return value && value.formatUnits().match( /\/[\s(]*?kg/ );
        } else {
            throw new Error( 'Dependence "' + variableId + '" not supported by isValueDependent' );
        }
    };

    /**
     * Define units
     */
    mw.calculators.addUnitsBases( {
        concentration: {
            toString: function( units ) {
                units = units.replace( ' pct', '%' );
                units = units.replace( 'ug', 'mcg' );

                return units;
            }
        },
        mass: {
            toString: function( units ) {
                units = units.replace( 'ug', 'mcg' );

                return units;
            }
        }
    } );

    mw.calculators.addUnits( {
        mcg: {
            baseName: 'mass',
            definition: '1 ug'
        },
        pct: {
            baseName: 'concentration',
            definition: '10 mg/mL'
        },
        units: {
            baseName: 'mass_abstract',
            aliases: [
                'unit'
            ]
        },
        vial: {
            baseName: 'volume_abstract'
        }
    } );



    /**
     * DrugColor
     */
    mw.calculators.drugColors = {};

    mw.calculators.addDrugColors = function( drugColorData ) {
        var drugColors = mw.calculators.createCalculatorObjects( 'DrugColor', drugColorData );

        for( var drugColorId in drugColors ) {
            mw.calculators.drugColors[ drugColorId ] = drugColors[ drugColorId ];
        }
    };

    mw.calculators.getDrugColor = function( drugColorId ) {
        if( mw.calculators.drugColors.hasOwnProperty( drugColorId ) ) {
            return mw.calculators.drugColors[ drugColorId ];
        } else {
            return null;
        }
    };

    /**
     * Class DrugColor
     * @param {Object} propertyValues
     * @returns {mw.calculators.objectClasses.DrugColor}
     * @constructor
     */
    mw.calculators.objectClasses.DrugColor = function( propertyValues ) {
        var properties = {
            required: [
                'id'
            ],
            optional: [
                'parentColor',
                'primaryColor',
                'highlightColor',
                'striped'
            ]
        };

        mw.calculators.objectClasses.CalculatorObject.call( this, properties, propertyValues );

        this.parentColor = this.parentColor || this.id === DEFAULT_DRUG_COLOR ? this.parentColor : DEFAULT_DRUG_COLOR;
    };

    mw.calculators.objectClasses.DrugColor.prototype = Object.create( mw.calculators.objectClasses.CalculatorObject.prototype );

    mw.calculators.objectClasses.DrugColor.prototype.getParentDrugColor = function() {
        if( !this.parentColor ) {
            return null;
        }

        var parentDrugColor = mw.calculators.getDrugColor( this.parentColor );

        if( !parentDrugColor ) {
            throw new Error( 'Parent drug color "' + this.parentColor + '" not found for drug color "' + this.id + '"' );
        }

        return parentDrugColor;
    };

    mw.calculators.objectClasses.DrugColor.prototype.getHighlightColor = function() {
        if( this.highlightColor ) {
            return this.highlightColor;
        } else if( this.parentColor ) {
            return this.getParentDrugColor().getHighlightColor();
        }
    };

    mw.calculators.objectClasses.DrugColor.prototype.getPrimaryColor = function() {
        if( this.primaryColor ) {
            return this.primaryColor;
        } else if( this.parentColor ) {
            return this.getParentDrugColor().getPrimaryColor();
        }
    };

    mw.calculators.objectClasses.DrugColor.prototype.isStriped = function() {
        if( this.striped !== null ) {
            return this.striped;
        } else if( this.parentColor ) {
            return this.getParentDrugColor().isStriped();
        }
    };





    /**
     * DrugPopulation
     */

    mw.calculators.drugPopulations = {};

    mw.calculators.addDrugPopulations = function( drugPopulationData ) {
        var drugPopulations = mw.calculators.createCalculatorObjects( 'DrugPopulation', drugPopulationData );

        for( var drugPopulationId in drugPopulations ) {
            mw.calculators.drugPopulations[ drugPopulationId ] = drugPopulations[ drugPopulationId ];
        }
    };

    mw.calculators.getDrugPopulation = function( drugPopulationId ) {
        if( mw.calculators.drugPopulations.hasOwnProperty( drugPopulationId ) ) {
            return mw.calculators.drugPopulations[ drugPopulationId ];
        } else {
            return null;
        }
    };



    /**
     * Class DrugPopulation
     * @param {Object} propertyValues
     * @returns {mw.calculators.objectClasses.DrugPopulation}
     * @constructor
     */
    mw.calculators.objectClasses.DrugPopulation = function( propertyValues ) {
        var properties = {
            required: [
                'id',
                'name'
            ],
            optional: [
                'abbreviation',
                'variables'
            ]
        };

        mw.calculators.objectClasses.CalculatorObject.call( this, properties, propertyValues );

        if( this.variables ) {
            for( var variableId in this.variables ) {
                if( !mw.calculators.getVariable( variableId ) ) {
                    throw new Error( 'DrugPopulation variable "' + variableId + '" not defined' );
                }

                this.variables[ variableId ].min = this.variables[ variableId ].hasOwnProperty( 'min' ) ?
                    math.unit( this.variables[ variableId ].min ) : null;

                this.variables[ variableId ].max = this.variables[ variableId ].hasOwnProperty( 'max' ) ?
                    math.unit( this.variables[ variableId ].max ) : null;
            }
        } else {
            this.variables = {};
        }
    };

    mw.calculators.objectClasses.DrugPopulation.prototype = Object.create( mw.calculators.objectClasses.CalculatorObject.prototype );

    mw.calculators.objectClasses.DrugPopulation.prototype.getCalculationData = function() {
        var inputData = new mw.calculators.objectClasses.CalculationData();

        for( var variableId in this.variables ) {
            inputData.variables.required.push( variableId );
        }

        return inputData;
    };

    mw.calculators.objectClasses.DrugPopulation.prototype.getCalculationDataScore = function( dataValues ) {
        // A return value of -1 indicates the data did not match the population definition

        for( var variableId in this.variables ) {
            if( !dataValues.hasOwnProperty( variableId ) ) {
                return -1;
            }

            if( this.variables[ variableId ].min &&
                ( !dataValues[ variableId ] ||
                    !math.largerEq( dataValues[ variableId ], this.variables[ variableId ].min ) ) ) {
                return -1;
            }

            if( this.variables[ variableId ].max &&
                ( !dataValues[ variableId ] ||
                    !math.smallerEq( dataValues[ variableId ], this.variables[ variableId ].max ) ) ) {
                return -1;
            }
        }

        // If the data matches the population definition, the score corresponds to the number of variables in the
        // population definition. This should roughly correspond to the specificity of the population.
        return Object.keys( this.variables ).length;
    };

    mw.calculators.objectClasses.DrugPopulation.prototype.toString = function() {
        return mw.calculators.isMobile() && this.abbreviation ? this.abbreviation : this.name;
    };



    /**
     * DrugRoute
     */
    mw.calculators.drugRoutes = {};

    mw.calculators.addDrugRoutes = function( drugRouteData ) {
        var drugRoutes = mw.calculators.createCalculatorObjects( 'DrugRoute', drugRouteData );

        for( var drugRouteId in drugRoutes ) {
            mw.calculators.drugRoutes[ drugRouteId ] = drugRoutes[ drugRouteId ];
        }
    };

    mw.calculators.getDrugRoute = function( drugRouteId ) {
        if( mw.calculators.drugRoutes.hasOwnProperty( drugRouteId ) ) {
            return mw.calculators.drugRoutes[ drugRouteId ];
        } else {
            return null;
        }
    };

    /**
     * Class DrugRoute
     * @param {Object} propertyValues
     * @returns {mw.calculators.objectClasses.DrugRoute}
     * @constructor
     */
    mw.calculators.objectClasses.DrugRoute = function( propertyValues ) {
        var properties = {
            required: [
                'id',
                'name'
            ],
            optional: [
                'abbreviation',
                'default'
            ]
        };

        mw.calculators.objectClasses.CalculatorObject.call( this, properties, propertyValues );
    };

    mw.calculators.objectClasses.DrugRoute.prototype = Object.create( mw.calculators.objectClasses.CalculatorObject.prototype );

    mw.calculators.objectClasses.DrugRoute.prototype.toString = function() {
        return mw.calculators.isMobile() && this.abbreviation ? this.abbreviation : this.name;
    };







    /**
     * DrugIndication
     */
    mw.calculators.drugIndications = {};

    mw.calculators.addDrugIndications = function( drugIndicationData ) {
        var drugIndications = mw.calculators.createCalculatorObjects( 'DrugIndication', drugIndicationData );

        for( var drugIndicationId in drugIndications ) {
            mw.calculators.drugIndications[ drugIndicationId ] = drugIndications[ drugIndicationId ];
        }
    };

    mw.calculators.getDrugIndication = function( drugIndicationId ) {
        if( mw.calculators.drugIndications.hasOwnProperty( drugIndicationId ) ) {
            return mw.calculators.drugIndications[ drugIndicationId ];
        } else {
            return null;
        }
    };

    /**
     * Class DrugIndication
     * @param {Object} propertyValues
     * @returns {mw.calculators.objectClasses.DrugIndication}
     * @constructor
     */
    mw.calculators.objectClasses.DrugIndication = function( propertyValues ) {
        var properties = {
            required: [
                'id',
                'name'
            ],
            optional: [
                'abbreviation',
                'default'
            ]
        };

        mw.calculators.objectClasses.CalculatorObject.call( this, properties, propertyValues );
    };

    mw.calculators.objectClasses.DrugIndication.prototype = Object.create( mw.calculators.objectClasses.CalculatorObject.prototype );

    mw.calculators.objectClasses.DrugIndication.prototype.toString = function() {
        return mw.calculators.isMobile() && this.abbreviation ? this.abbreviation : this.name;
    };





    /**
     * Drug
     */
    mw.calculators.drugs = {};

    mw.calculators.addDrugs = function( drugData ) {
        var drugs = mw.calculators.createCalculatorObjects( 'Drug', drugData );

        for( var drugId in drugs ) {
            mw.calculators.drugs[ drugId ] = drugs[ drugId ];

            var drugDosageCalculationId = mw.calculators.getDrugDosageCalculationId( drugId );
            var drugDosageCalculation = mw.calculators.getCalculation( drugDosageCalculationId );

            if( !drugDosageCalculation ) {
                var calculationData = {};

                calculationData[ drugDosageCalculationId ] = {
                    calculate: mw.calculators.objectClasses.DrugDosageCalculation.prototype.calculate,
                    drug: drugId,
                    type: 'drug'
                };

                mw.calculators.addCalculations( calculationData, 'DrugDosageCalculation' );

                drugDosageCalculation = mw.calculators.getCalculation( drugDosageCalculationId );
            }

            drugDosageCalculation.setDependencies();
        }
    };

    mw.calculators.addDrugDosages = function( drugId, drugDosageData ) {
        var drug = mw.calculators.getDrug( drugId );

        if( !drug ) {
            throw new Error( 'DrugDosage references drug "' + drugId + '" which is not defined' );
        }

        drug.addDosages( drugDosageData );

        // Update calculation dependencies
        var drugDosageCalculation = mw.calculators.getCalculation( mw.calculators.getDrugDosageCalculationId( drugId ) );

        drugDosageCalculation.updateVariables();
        drugDosageCalculation.setDependencies();
    };

    mw.calculators.getDrug = function( drugId ) {
        if( mw.calculators.drugs.hasOwnProperty( drugId ) ) {
            return mw.calculators.drugs[ drugId ];
        } else {
            return null;
        }
    };



    /**
     * Class Drug
     * @param {Object} propertyValues
     * @returns {mw.calculators.objectClasses.Drug}
     * @constructor
     */
    mw.calculators.objectClasses.Drug = function( propertyValues ) {
        mw.calculators.objectClasses.CalculatorObject.call( this, this.getProperties(), propertyValues );

        if( !this.color ) {
            this.color = DEFAULT_DRUG_COLOR;
        }

        var color = mw.calculators.getDrugColor( this.color );

        if( !color ) {
            throw new Error( 'Invalid drug color "' + this.color + '" for drug "' + this.id + '"' );
        }

        this.color = color;

        if( this.preparations ) {
            var preparationData = this.preparations;

            this.preparations = [];

            this.addPreparations( preparationData );
        } else {
            this.preparations = [];
        }

        if( this.dosages ) {
            var dosageData = this.dosages;

            this.dosages = [];

            this.addDosages( dosageData );
        } else {
            this.dosages = [];
        }

        this.references = this.references ? this.references : [];
    };

    mw.calculators.objectClasses.Drug.prototype = Object.create( mw.calculators.objectClasses.CalculatorObject.prototype );

    mw.calculators.objectClasses.Drug.prototype.addDosages = function( dosageData ) {
        var dosages = mw.calculators.createCalculatorObjects( 'DrugDosage', dosageData );

        for( var dosageId in dosages ) {
            dosages[ dosageId ].id = this.dosages.length;

            this.dosages.push( dosages[ dosageId ] );
        }
    };

    mw.calculators.objectClasses.Drug.prototype.addPreparations = function( preparationData ) {
        var preparations = mw.calculators.createCalculatorObjects( 'DrugPreparation', preparationData );

        for( var preparationId in preparations ) {
            preparations[ preparationId ].id = this.preparations.length;

            this.preparations.push( preparations[ preparationId ] );
        }
    };

    mw.calculators.objectClasses.Drug.prototype.getIndications = function() {
        var indications = [];

        for( var iDosage in this.dosages ) {
            if( this.dosages[ iDosage ].indication ) {
                indications.push( this.dosages[ iDosage ].indication );
            }
        }

        return indications.filter( mw.calculators.uniqueValues );
    };

    mw.calculators.objectClasses.Drug.prototype.getPopulations = function( indicationId ) {
        var populations = [];

        for( var iDosage in this.dosages ) {
            if( this.dosages[ iDosage ].population &&
                ( !indicationId || ( this.dosages[ iDosage ].indication && this.dosages[ iDosage ].indication.id === indicationId ) ) ) {
                populations.push( this.dosages[ iDosage ].population );
            }
        }

        return populations.filter( mw.calculators.uniqueValues );
    };

    mw.calculators.objectClasses.Drug.prototype.getRoutes = function( indicationId ) {
        var routes = [];

        for( var iDosage in this.dosages ) {
            if( this.dosages[ iDosage ].route &&
                ( !indicationId || ( this.dosages[ iDosage ].indication && this.dosages[ iDosage ].indication.id === indicationId ) ) ) {
                routes.push( this.dosages[ iDosage ].route );
            }
        }

        return routes.filter( mw.calculators.uniqueValues );
    };

    mw.calculators.objectClasses.Drug.prototype.getPreparations = function( excludeDilutionRequired ) {
        var preparations = this.preparations.filter( mw.calculators.uniqueValues );

        if( excludeDilutionRequired ) {
            for( var iPreparation in preparations ) {
                if( preparations[ iPreparation ].dilutionRequired ) {
                    delete preparations[ iPreparation ];
                }
            }
        }

        return preparations;
    };

    mw.calculators.objectClasses.Drug.prototype.getProperties = function() {
        return {
            required: [
                'id',
                'name'
            ],
            optional: [
                'color',
                'description',
                'dosages',
                'preparations',
                'references'
            ]
        };
    };





    /**
     * DrugPreparation
     */
    mw.calculators.addDrugPreparations = function( drugId, drugPreparationData ) {
        var drug = mw.calculators.getDrug( drugId );

        if( !drug ) {
            throw new Error( 'DrugPreparation references drug "' + drugId + '" which is not defined' );
        }

        drug.addPreparations( drugPreparationData );

        var drugDosageCalculation = mw.calculators.getCalculation( mw.calculators.getDrugDosageCalculationId( drugId ) );

        drugDosageCalculation.recalculate();
    };



    /**
     * Class DrugPreparation
     * @param {Object} propertyValues
     * @returns {mw.calculators.objectClasses.DrugPreparation}
     * @constructor
     */
    mw.calculators.objectClasses.DrugPreparation = function( propertyValues ) {
        var properties = {
            required: [
                'id',
                'concentration'
            ],
            optional: [
                'default',
                'dilutionRequired',
                'commonDilution'
            ]
        };

        mw.calculators.objectClasses.CalculatorObject.call( this, properties, propertyValues );


        this.concentration = this.concentration.replace( 'mcg', 'ug' );

        this.concentration = math.unit( this.concentration );
    };

    mw.calculators.objectClasses.DrugPreparation.prototype = Object.create( mw.calculators.objectClasses.CalculatorObject.prototype );

    mw.calculators.objectClasses.DrugPreparation.prototype.getVolumeUnits = function() {
        // The units of concentration will always be of the form "mass / volume"
        // The regular expression matches all text leading up to the volume units
        return mw.calculators.getUnitsByBase( this.concentration ).volume;
    };

    mw.calculators.objectClasses.DrugPreparation.prototype.toString = function() {
        return mw.calculators.getValueString( this.concentration );
    };





    /**
     * Class DrugDosage
     * @param {Object} propertyValues
     * @returns {mw.calculators.objectClasses.DrugDosage}
     * @constructor
     */
    mw.calculators.objectClasses.DrugDosage = function( propertyValues ) {
        mw.calculators.objectClasses.CalculatorObject.call( this, this.getProperties(), propertyValues );

        var drugIndication = mw.calculators.getDrugIndication( this.indication );

        if( !drugIndication ) {
            throw new Error( 'Invalid indication "' + this.indication + '" for drug dosage' );
        }

        this.indication = drugIndication;

        this.population = this.population ? this.population : DEFAULT_DRUG_POPULATION;

        var drugPopulation = mw.calculators.getDrugPopulation( this.population );

        if( !drugPopulation ) {
            throw new Error( 'Invalid population "' + this.population + '" for drug dosage' );
        }

        this.population = drugPopulation;

        this.references = this.references ? this.references : [];

        this.route = this.route ? this.route : DEFAULT_DRUG_ROUTE;

        var drugRoute = mw.calculators.getDrugRoute( this.route );

        if( !drugRoute ) {
            throw new Error( 'Invalid route "' + this.route + '" for drug dosage' );
        }

        this.route = drugRoute;

        // Add the dose objects to the drug
        var drugDoseData = this.dose;
        this.dose = [];

        this.addDoses( drugDoseData );
    };

    mw.calculators.objectClasses.DrugDosage.prototype = Object.create( mw.calculators.objectClasses.CalculatorObject.prototype );

    mw.calculators.objectClasses.DrugDosage.prototype.addDoses = function( drugDoseData ) {
        // Each dosage can have one or more associated doses. Ensure this value is an array.
        if( !Array.isArray( drugDoseData ) ) {
            drugDoseData = [ drugDoseData ];
        }

        var doses = mw.calculators.createCalculatorObjects( 'DrugDose', drugDoseData );

        for( var doseId in doses ) {
            doses[ doseId ].id = this.dose.length;

            this.dose.push( doses[ doseId ] );
        }
    };

    mw.calculators.objectClasses.DrugDosage.prototype.getCalculationData = function() {
        var inputData = new mw.calculators.objectClasses.CalculationData();

        inputData = inputData.merge( this.population.getCalculationData() );

        for( var iDose in this.dose ) {
            inputData = inputData.merge( this.dose[ iDose ].getCalculationData() );
        }

        return inputData;
    };

    mw.calculators.objectClasses.DrugDosage.prototype.getProperties = function() {
        return {
            required: [
                'dose',
                'id',
                'indication'
            ],
            optional: [
                'description',
                'population',
                'route',
                'references'
            ]
        };
    };

    mw.calculators.objectClasses.DrugDosage.prototype.hasInfo = function() {
        return this.description;
    };





    /**
     * Class DrugDose
     * @param {Object} propertyValues
     * @returns {mw.calculators.objectClasses.DrugDose}
     * @constructor
     */
    mw.calculators.objectClasses.DrugDose = function( propertyValues ) {
        mw.calculators.objectClasses.CalculatorObject.call( this, this.getProperties(), propertyValues );

        if( this.weightCalculation ) {
            var weightCalculationIds = this.weightCalculation;

            // weightCalculation property will contain references to the actual objects, so reinitialize
            this.weightCalculation = [];

            if( !Array.isArray( weightCalculationIds ) ) {
                weightCalculationIds = [ weightCalculationIds ];
            }

            for( var iWeightCalculation in weightCalculationIds ) {
                var weightCalculationId = weightCalculationIds[ iWeightCalculation ];
                var weightCalculation = mw.calculators.getCalculation( weightCalculationId );

                if( !weightCalculation ) {
                    throw new Error( 'Drug dose references weight calculation "' + weightCalculationId + '" which is not defined' );
                }

                this.weightCalculation.push( weightCalculation );
            }
        } else {
            this.weightCalculation = [];
        }

        var mathProperties = this.getMathProperties();
        var isWeightDependent = false;

        for( var iMathProperty in mathProperties ) {
            var mathProperty = mathProperties[ iMathProperty ];

            if( this[ mathProperty ] ) {
                // TODO consider making a UnitsBase.weight.fromString()
                this[ mathProperty ] = this[ mathProperty ].replace( 'kg', 'kgwt' );
                this[ mathProperty ] = this[ mathProperty ].replace( 'mcg', 'ug' );

                this[ mathProperty ] = math.unit( this[ mathProperty ] );

                if( mw.calculators.isValueDependent( this[ mathProperty ], 'weight' ) ) {
                    isWeightDependent = true;
                }
            } else {
                this[ mathProperty ] = null;
            }
        }

        if( isWeightDependent ) {
            // Default is tbw
            this.weightCalculation.push( mw.calculators.getCalculation( 'tbw' ) );
        }
    };

    mw.calculators.objectClasses.DrugDose.prototype = Object.create( mw.calculators.objectClasses.CalculatorObject.prototype );

    mw.calculators.objectClasses.DrugDose.prototype.getAdministration = function() {
        var administration = '';

        if( this.frequency ) {
            administration += administration ? ' ' : '';
            administration += this.frequency;
        }

        if( this.duration ) {
            administration += administration ? ' ' : '';
            administration += 'over ' + this.duration;
        }

        return administration;
    };

    mw.calculators.objectClasses.DrugDose.prototype.getCalculationData = function() {
        var calculationData = new mw.calculators.objectClasses.CalculationData();

        for( var iWeightCalculation in this.weightCalculation ) {
            calculationData.calculations.optional.push( this.weightCalculation[ iWeightCalculation ].id );
        }

        return calculationData;
    };

    mw.calculators.objectClasses.DrugDose.prototype.getMathProperties = function() {
        return [
            'dose',
            'min',
            'max',
            'absoluteMin',
            'absoluteMax'
        ];
    };

    mw.calculators.objectClasses.DrugDose.prototype.getProperties = function() {
        return {
            required: [
                'id'
            ],
            optional: [
                'absoluteMax',
                'absoluteMin',
                'dose',
                'duration',
                'frequency',
                'min',
                'max',
                'name',
                'weightCalculation'
            ]
        };
    };




    mw.calculators.getDrugDosageCalculationId = function( drugId ) {
        return 'drugDosages-' + drugId;
    };

    /**
     * Class DrugDosageCalculation
     * @param {Object} propertyValues
     * @returns {mw.calculators.objectClasses.DrugDosageCalculation}
     * @constructor
     */
    mw.calculators.objectClasses.DrugDosageCalculation = function( propertyValues ) {
        mw.calculators.objectClasses.CalculatorObject.call( this, this.getProperties(), propertyValues );

        this.initialize();
    };

    mw.calculators.objectClasses.DrugDosageCalculation.prototype = Object.create( mw.calculators.objectClasses.AbstractCalculation.prototype );

    mw.calculators.objectClasses.DrugDosageCalculation.prototype.calculate = function( data ) {
        var value = {
            message: null,
            population: null,
            preparation: data.preparation,
            dose: []
        };

        this.activeDosageId = null;

        if( !data.drug.dosages.length ) {
            value.message = 'No dose data';

            return value;
        }

        // Determine which dosage to use
        var populationScores = [];

        for( var iDosage in data.drug.dosages ) {
            var drugDosage = data.drug.dosages[ iDosage ];

            // If the indication and route do not match, set the score to -1
            var populationScore =
                drugDosage.indication.id === data.indication.id && drugDosage.route.id === data.route.id ?
                drugDosage.population.getCalculationDataScore( data ) : -1;

            populationScores.push( populationScore );
        }

        var maxPopulationScore = Math.max.apply( null, populationScores );

        if( maxPopulationScore < 0 ) {
            value.message = 'No dose data for indication "' + String( data.indication ) + '" and route "' + String( data.route ) + '"';

            return value;
        }

        // If there is more than one dosage with the same score, take the first.
        // This allows the data editor to decide which is most important.
        this.activeDosageId = populationScores.indexOf( maxPopulationScore );

        var dosage = data.drug.dosages[ this.activeDosageId ];

        // A dosage may contain multiple doses (e.g. induction and maintenance)
        for( var iDose in dosage.dose ) {
            var dose = dosage.dose[ iDose ];
            var mathProperties = dose.getMathProperties();

            var weightCalculation = null;
            var weightValue = null;

            // data.weightCalculation should be in order of preference, so take the first non-null value
            for( var iWeightCalculation in dose.weightCalculation ) {
                if( dose.weightCalculation[ iWeightCalculation ].value !== null ) {
                    weightCalculation = dose.weightCalculation[ iWeightCalculation ];
                    weightValue = dose.weightCalculation[ iWeightCalculation ].value;

                    break;
                }
            }

            // Initialize value properties for dose
            value.dose[ iDose ] = {
                massPerWeight: {},
                mass: {},
                volume: {},
                weightCalculation: weightCalculation ? weightCalculation : null
            };

            var massUnits;
            var volumeUnits;

            for( var iMathProperty in mathProperties ) {
                var mathProperty = mathProperties[ iMathProperty ];

                var doseValue = dose[ mathProperty ];

                if( doseValue ) {
                    var doseUnitsByBase = mw.calculators.getUnitsByBase( doseValue );

                    if( doseUnitsByBase.hasOwnProperty( 'weight' ) ) {
                        value.dose[ iDose ].massPerWeight[ mathProperty ] = doseValue;

                        if( weightValue ) {
                            massUnits = doseUnitsByBase.mass;

                            if( doseUnitsByBase.hasOwnProperty( 'time' ) ) {
                                massUnits += '/' + doseUnitsByBase.time;
                            }

                            // For whatever reason math.format will simplify the units, but math.formatUnits will not
                            // as a hack, we recreate a new unit value with the correct formatting of the result
                            value.dose[ iDose ].mass[ mathProperty ] = math.unit( math.multiply( doseValue, weightValue ).format() ).to( massUnits );
                        }
                    } else {
                        value.dose[ iDose ].mass[ mathProperty ] = doseValue;
                    }

                    if( data.preparation && value.dose[ iDose ].mass[ mathProperty ] ) {
                        // Need a special case for pct
                        if( data.preparation.concentration.formatUnits() === 'pct' ) {
                            volumeUnits = 'mL';
                        } else {
                            var preparationUnitsByBase = mw.calculators.getUnitsByBase( data.preparation.concentration );

                            volumeUnits = preparationUnitsByBase.volume;
                        }

                        if( doseUnitsByBase.hasOwnProperty( 'time' ) ) {
                            volumeUnits += '/' + doseUnitsByBase.time;
                        }

                        // Same hack as above to get units to simplify correctly
                        value.dose[ iDose ].volume[ mathProperty ] = math.unit( math.multiply( value.dose[ iDose ].mass[ mathProperty ], math.divide( 1, data.preparation.concentration ) ).format() ).to( volumeUnits );
                    }
                }
            }

            if( value.dose[ iDose ].mass.hasOwnProperty( 'absoluteMin' ) ) {
                if( value.dose[ iDose ].mass.hasOwnProperty( 'max' ) && math.larger( value.dose[ iDose ].mass.absoluteMin, value.dose[ iDose ].mass.max ) ) {
                    // Both min and max are larger than the absolute max dose, so just convert to single dose.
                    value.dose[ iDose ].mass.dose = value.dose[ iDose ].mass.absoluteMin;

                    delete value.dose[ iDose ].mass.min;
                    delete value.dose[ iDose ].mass.max;

                    if( value.dose[ iDose ].volume.hasOwnProperty( 'absoluteMin' ) ) {
                        value.dose[ iDose ].volume.dose = value.dose[ iDose ].volume.absoluteMin;

                        delete value.dose[ iDose ].volume.min;
                        delete value.dose[ iDose ].volume.max;
                    }
                } else if( value.dose[ iDose ].mass.hasOwnProperty( 'min' ) && math.larger( value.dose[ iDose ].mass.absoluteMin, value.dose[ iDose ].mass.min ) ) {
                    value.dose[ iDose ].mass.min = value.dose[ iDose ].mass.absoluteMin;

                    if( value.dose[ iDose ].volume.hasOwnProperty( 'absoluteMin' ) ) {
                        value.dose[ iDose ].volume.min = value.dose[ iDose ].volume.absoluteMin;
                    }
                } else if( value.dose[ iDose ].mass.hasOwnProperty( 'dose' ) && math.larger( value.dose[ iDose ].mass.absoluteMin, value.dose[ iDose ].mass.dose ) ) {
                    value.dose[ iDose ].mass.dose = value.dose[ iDose ].mass.absoluteMin;

                    if( value.dose[ iDose ].volume.hasOwnProperty( 'absoluteMin' ) ) {
                        value.dose[ iDose ].volume.dose = value.dose[ iDose ].volume.absoluteMin;
                    }
                }
            }

            if( value.dose[ iDose ].mass.hasOwnProperty( 'absoluteMax' ) ) {
                if( value.dose[ iDose ].mass.hasOwnProperty( 'min' ) && math.smaller( value.dose[ iDose ].mass.absoluteMax, value.dose[ iDose ].mass.min ) ) {
                    // Both min and max are larger than the absolute max dose, so just convert to single dose.
                    value.dose[ iDose ].mass.dose = value.dose[ iDose ].mass.absoluteMax;

                    delete value.dose[ iDose ].mass.min;
                    delete value.dose[ iDose ].mass.max;

                    if( value.dose[ iDose ].volume.hasOwnProperty( 'absoluteMax' ) ) {
                        value.dose[ iDose ].volume.dose = value.dose[ iDose ].volume.absoluteMax;

                        delete value.dose[ iDose ].volume.min;
                        delete value.dose[ iDose ].volume.max;
                    }
                } else if( value.dose[ iDose ].mass.hasOwnProperty( 'max' ) && math.smaller( value.dose[ iDose ].mass.absoluteMax, value.dose[ iDose ].mass.max ) ) {
                    value.dose[ iDose ].mass.max = value.dose[ iDose ].mass.absoluteMax;

                    if( value.dose[ iDose ].volume.hasOwnProperty( 'absoluteMax' ) ) {
                        value.dose[ iDose ].volume.max = value.dose[ iDose ].volume.absoluteMax;
                    }
                } else if( value.dose[ iDose ].mass.hasOwnProperty( 'dose' ) && math.smaller( value.dose[ iDose ].mass.absoluteMax, value.dose[ iDose ].mass.dose ) ) {
                    value.dose[ iDose ].mass.dose = value.dose[ iDose ].mass.absoluteMax;

                    if( value.dose[ iDose ].volume.hasOwnProperty( 'absoluteMax' ) ) {
                        value.dose[ iDose ].volume.dose = value.dose[ iDose ].volume.absoluteMax;
                    }
                }
            }
        }

        return value;
    };

    mw.calculators.objectClasses.DrugDosageCalculation.prototype.doRender = function() {
        var $calculationContainer = $( '.' + this.getContainerId() );

        if( !$calculationContainer.length ) {
            return;
        }

        // Add all required classes
        $calculationContainer.addClass( 'border ' + this.getContainerClasses() );

        // Add search phrases
        $calculationContainer.attr( 'data-search', this.getSearchString() );

        // Store this object in a local variable since .each() will reassign this to the DOM object of each
        // calculation container.
        var calculation = this;
        var calculationCount = 0;

        // Eventually may implement different rendering, so we should regenerate
        // all elements with each iteration of the loop.
        // I.e. might show result in table and inline in 2 different places of article.
        $calculationContainer.each( function() {
            // Initalize the variables for all the elements of the calculation. These need to be in order of placement
            // in the calculation container
            var elementTypes = [
                'title',
                'dosage',
                'info'
            ];

            var elements = {};

            for( var iElementType in elementTypes ) {
                var elementType = elementTypes[ iElementType ];

                // If an input contained by $container has user input focus, $container will not rerender (would be
                // annoying behavior to the user). However, if it contains subelements which should try to rerender,
                // add those elements to the contains property.
                elements[ elementType ] = {
                    $container: null,
                    contains: [],
                    id: calculation.getContainerId() + '-' + elementType
                };

                if( calculationCount ) {
                    elements[ elementType ].id += '-' + calculationCount;
                }
            }

            // Create title element and append to container
            elements.title.$container = $( '<div>', {
                id: elements.title.id,
                class: 'col-12 border-bottom ' + calculation.getElementClasses( 'title' )
            } );

            elements.title.$container.append( calculation.getTitleHtml() );

            if( calculation.hasInfo() ) {
                // Id of the info container should already be set by getInfo()
                elements.info.$container = calculation.getInfo();
            }

            // Create the dosage element
            elements.dosage.$container = $( '<div>', {
                id: elements.dosage.id,
                class: 'row no-gutters ' + calculation.getElementClasses( 'dosage' )
            } );

            // Dose column
            var $dose = $( '<div>', {
                id: calculation.getContainerId() + '-dose',
                class: 'col-7 ' + calculation.getElementClasses( 'dose' )
            }  );

            var dash = '-';

            // The options column should only show the preparation if there is a calculated volume
            var hasVolume;

            if( !calculation.value || calculation.activeDosageId === null ) {
                if( calculation.value && calculation.value.hasOwnProperty( 'message' ) ) {
                    $dose.append( $( '<i>' ).append( calculation.value.message ) );
                }
            } else {
                var dosage = calculation.drug.dosages[ calculation.activeDosageId ];

                if( dosage.population && dosage.population.id !== DEFAULT_DRUG_POPULATION ) {
                    var $dosePopulation = $( '<div>', {
                        class: calculation.getElementClasses( 'dose-info' )
                    } );

                    $dosePopulation
                        .append( $( '<div>', {
                            class: calculation.getElementClasses( 'dose-info-population' )
                        } ).append( String( dosage.population ) + ' dosing' ) );

                    $dose.append( $dosePopulation );
                }

                var $doseData = $( '<div>', {
                    class: calculation.getElementClasses( 'dose-data' )
                } );

                // This will iterate through the calculated doses. iDose should exactly correspond to doses within dosage
                // to allow referencing other properties of the dose.
                for( var iDose in calculation.value.dose ) {
                    var dose = dosage.dose[ iDose ];
                    var doseValue = calculation.value.dose[ iDose ];

                    if( dose.name ) {
                        $doseData.append( dose.name + '<br />' );
                    }

                    var $doseList = $( '<ul>' );

                    var administration = dose.getAdministration();
                    var administrationDisplayed = false;

                    var massPerWeightHtml = '';

                    if( doseValue.massPerWeight.hasOwnProperty( 'dose' ) ) {
                        massPerWeightHtml += mw.calculators.getValueString( doseValue.massPerWeight.dose );
                    } else if( doseValue.massPerWeight.hasOwnProperty( 'min' ) &&
                        doseValue.massPerWeight.hasOwnProperty( 'max' ) ) {

                        // getValueString will simplify the value and may adjust the units
                        var massPerWeightMinValue = math.unit( mw.calculators.getValueString( doseValue.massPerWeight.min ) );
                        var massPerWeightMaxValue = math.unit( mw.calculators.getValueString( doseValue.massPerWeight.max ) );

                        if( massPerWeightMinValue.formatUnits() !== massPerWeightMaxValue.formatUnits() ) {
                            // If the units between min and max don't match, show both
                            massPerWeightHtml += mw.calculators.getValueString( massPerWeightMinValue );
                        } else {
                            massPerWeightHtml += mw.calculators.getValueNumber( massPerWeightMinValue );
                        }

                        massPerWeightHtml += dash;
                        massPerWeightHtml += mw.calculators.getValueString( massPerWeightMaxValue );
                    }

                    if( massPerWeightHtml ) {
                        if( administration && ! administrationDisplayed ) {
                            massPerWeightHtml += ' ' + administration;
                            administrationDisplayed = true;
                        }

                        var massPerWeightNotesHtml = '';

                        if( doseValue.mass.hasOwnProperty( 'absoluteMin' ) ) {
                            massPerWeightNotesHtml += 'Min: ' + mw.calculators.getValueString( doseValue.mass.absoluteMin );
                        } else if( doseValue.mass.hasOwnProperty( 'absoluteMax' ) ) {
                            massPerWeightNotesHtml += 'Max: ' + mw.calculators.getValueString( doseValue.mass.absoluteMax );
                        }

                        if( dose.weightCalculation && dose.weightCalculation[ 0 ].id !== 'tbw' ) {
                            if( massPerWeightNotesHtml ) {
                                massPerWeightNotesHtml += ', ';
                            }

                            massPerWeightNotesHtml += dose.weightCalculation[ 0 ].getTitleString();
                        }

                        if( massPerWeightNotesHtml ) {
                            massPerWeightHtml += ' (' + massPerWeightNotesHtml + ')';
                        }

                        massPerWeightHtml = $( '<li>' ).append( massPerWeightHtml );

                        $doseList.append( massPerWeightHtml );
                    }

                    var massHtml = '';

                    if( doseValue.mass.hasOwnProperty( 'dose' ) ) {
                        massHtml += mw.calculators.getValueString( doseValue.mass.dose );
                    } else if( doseValue.mass.hasOwnProperty( 'min' ) &&
                        doseValue.mass.hasOwnProperty( 'max' ) ) {

                        // getValueString will simplify the value and may adjust the units
                        var massMinValue = math.unit( mw.calculators.getValueString( doseValue.mass.min ) );
                        var massMaxValue = math.unit( mw.calculators.getValueString( doseValue.mass.max ) );

                        if( massMinValue.formatUnits() !== massMaxValue.formatUnits() ) {
                            // If the units between min and max don't match, show both
                            massHtml += mw.calculators.getValueString( massMinValue );
                        } else {
                            massHtml += mw.calculators.getValueNumber( massMinValue );
                        }

                        massHtml += dash;
                        massHtml += mw.calculators.getValueString( massMaxValue );
                    }

                    if( massHtml ) {
                        if( administration && ! administrationDisplayed ) {
                            massHtml += ' ' + administration;
                            administrationDisplayed = true;
                        }

                        if( dose.weightCalculation.length && doseValue.weightCalculation.id !== dose.weightCalculation[ 0 ].id ) {
                            var weightCalculationLabel = doseValue.weightCalculation.getTitleString();

                            var $weightCalculationInfoIcon = $( '<i>', {
                                class: 'far fa-question-circle',
                                'data-toggle': 'popover',
                                'data-trigger': 'focus',
                                'data-content': String( doseValue.weightCalculation ) +
                                    ' is being used because data is missing for ' +
                                    String( dose.weightCalculation[ 0 ] ) + ': ' + dose.weightCalculation[ 0 ].message
                            } );

                            massHtml += '&nbsp; (' + weightCalculationLabel + '&nbsp;' + $weightCalculationInfoIcon[ 0 ].outerHTML + ')';
                        }

                        massHtml = $( '<li>' ).append( massHtml );

                        $doseList.append( massHtml );
                    }

                    var volumeHtml = '';

                    if( doseValue.volume.hasOwnProperty( 'dose' ) ) {
                        volumeHtml += mw.calculators.getValueString( doseValue.volume.dose );
                    } else if( doseValue.volume.hasOwnProperty( 'min' ) &&
                        doseValue.volume.hasOwnProperty( 'max' ) ) {

                        // getValueString will simplify the value and may adjust the units
                        var volumeMinValue = math.unit( mw.calculators.getValueString( doseValue.volume.min ) );
                        var volumeMaxValue = math.unit( mw.calculators.getValueString( doseValue.volume.max ) );

                        if( volumeMinValue.formatUnits() !== volumeMaxValue.formatUnits() ) {
                            // If the units between min and max don't match, show both
                            volumeHtml += mw.calculators.getValueString( volumeMinValue );
                        } else {
                            volumeHtml += mw.calculators.getValueNumber( volumeMinValue );
                        }

                        volumeHtml += dash;
                        volumeHtml += mw.calculators.getValueString( doseValue.volume.max );
                    }

                    if( volumeHtml ) {
                        if( administration && ! administrationDisplayed ) {
                            volumeHtml += ' ' + administration;
                            administrationDisplayed = true;
                        }

                        volumeHtml = $( '<li>' ).append( volumeHtml );

                        $doseList.append( volumeHtml );

                        hasVolume = true;
                    }

                    $doseData.append( $doseList );
                }

                $dose.append( $doseData );

                if( calculation.hasInfo() ) {
                    $dose.append( calculation.getInfoButton( calculationCount ) );
                }
            }


            // Options column
            var $options = $( '<div>', {
                id: calculation.getContainerId() + '-options',
                class: 'col-5 ' + calculation.getElementClasses( 'options' )
            } );

            var optionsRowClass = 'row no-gutters align-items-center';

            if( !mw.calculators.isMobile() ) {
                optionsRowClass += ' mb-2';
            }

            var optionLabelClass = mw.calculators.isMobile() ? 'col-4' : 'col-3';
            var optionValueClass = mw.calculators.isMobile() ? 'col-8' : 'col-9';

            var indications = calculation.drug.getIndications();

            if( indications.length ) {
                var indicationVariable = mw.calculators.getVariable( calculation.getVariableIds().indication );

                $options
                    .append( $( '<div>', {
                        class: optionsRowClass
                    } )
                        .append(
                            $( '<div>', {
                                class: optionLabelClass,
                                html: indicationVariable.getLabelString() + '&nbsp;'
                            } ),
                            $( '<div>', {
                                class: optionValueClass
                            } )
                                .append( indicationVariable.createInput({
                                    class: 'calculator-container-input-DrugDosageCalculator-options',
                                    hideLabel: true,
                                    inline: true
                                } ) ) ) );
            }

            var routes = calculation.drug.getRoutes();

            if( routes.length ) {
                var routeVariable = mw.calculators.getVariable( calculation.getVariableIds().route );

                $options
                    .append( $( '<div>', {
                        class: optionsRowClass
                    } )
                        .append(
                            $( '<div>', {
                                class: optionLabelClass,
                                html: routeVariable.getLabelString() + '&nbsp;'
                            } ),
                            $( '<div>', {
                                class: optionValueClass
                            } )
                                .append( routeVariable.createInput({
                                    class: 'calculator-container-input-DrugDosageCalculator-options',
                                    hideLabel: true,
                                    inline: true
                                } ) ) ) );
            }

            // Don't show preparations if there isn't a dose with volume
            if( hasVolume ) {
                var preparations = calculation.drug.getPreparations();

                if( preparations.length ) {
                    var preparationVariable = mw.calculators.getVariable( calculation.getVariableIds().preparation );

                    $options
                        .append( $( '<div>', {
                            class: optionsRowClass
                        } )
                            .append(
                                $( '<div>', {
                                    class: optionLabelClass,
                                    html: preparationVariable.getLabelString() + '&nbsp;'
                                } ),
                                $( '<div>', {
                                    class: optionValueClass
                                } )
                                    .append( preparationVariable.createInput({
                                        class: 'calculator-container-input-DrugDosageCalculator-options',
                                        hideLabel: true,
                                        inline: true
                                    } ) ) ) );
                }
            }

            elements.dosage.$container.append( $dose, $options );

            // Add elements to the contains array
            elements.dosage.contains.push( $dose, $options );

            // Iterate over elementTypes since it is in order of rendering
            for( var iElementType in elementTypes ) {
                var elementType = elementTypes[ iElementType ];
                var element = elements[ elementType ];

                var $existingContainer = $( '#' + element.id );

                if( $existingContainer.length ) {
                    // If an input within this container has focus (i.e. the user changed a variable input which
                    // triggered this rerender), don't rerender the element as this would destroy the focus on
                    // the input.
                    if( !$.contains( $existingContainer[ 0 ], $( ':focus' )[ 0 ] ) ) {
                        $existingContainer.replaceWith( element.$container );
                    } else {
                        for( var containedElementId in element.contains ) {
                            var $containedElement = element.contains[ containedElementId ];

                            var $existingContainedContainer = $( '#' + $containedElement.attr( 'id' ) );

                            if( $existingContainedContainer.length ) {
                                if( !$.contains( $existingContainedContainer[ 0 ], $( ':focus' )[ 0 ] ) ) {
                                    $existingContainedContainer.replaceWith( $containedElement );
                                }
                            }
                        }
                    }
                } else {
                    $( this ).append( elements[ elementType ].$container );
                }
            }

            calculationCount++;
        } );

        // Activate popovers
        $( '[data-toggle="popover"]' ).popover();
    };

    mw.calculators.objectClasses.DrugDosageCalculation.prototype.getCalculationData = function() {
        var inputData = new mw.calculators.objectClasses.CalculationData();

        // Add variables created by this calculation
        var variableIds = this.getVariableIds();

        for( var variableType in variableIds ) {
            inputData.variables.optional.push( variableIds[ variableType ] );
        }

        var dataTypes = inputData.getDataTypes();

        // Data is only actually required if it is required by every dosage for the drug.
        // Data marked as required by an individual dosage that does not appear in every
        // dosage will be converted to optional.
        var requiredInputData = new mw.calculators.objectClasses.CalculationData();

        // Need a way to tell the first iteration of the loop to initialize the required variables to a value that
        // is distinct from the empty array (populated across loop using array intersect, so could become [] and shouldn't
        // reinitialize).
        var initializeRequiredData = true;

        // Iterate through each dosage to determine variable dependency
        for( var iDosage in this.drug.dosages ) {
            var dosageInputData = this.drug.dosages[ iDosage ].getCalculationData();

            inputData = inputData.merge( dosageInputData );

            for( var iDataType in dataTypes ) {
                var dataType = dataTypes[ iDataType ];

                if( initializeRequiredData ) {
                    requiredInputData[ dataType ].required = inputData[ dataType ].required;
                } else {
                    // Data is only truly required if it is required by all dosage calculations, so use array intersection
                    requiredInputData[ dataType ].required = requiredInputData[ dataType ].required.filter( function( index ) {
                        return dosageInputData[ dataType ].required.indexOf( index ) !== -1;
                    } );
                }
            }

            initializeRequiredData = false;
        }

        for( var iDataType in dataTypes ) {
            var dataType = dataTypes[ iDataType ];

            // Move any data marked required in inputData to optional if it not actually required (i.e. doesn't appear
            // in requiredInputData).
            inputData[ dataType ].optional = inputData[ dataType ].optional.concat( inputData[ dataType ].required.filter( function( index ) {
                return requiredInputData[ dataType ].required.indexOf( index ) === -1;
            } ) ).filter( mw.calculators.uniqueValues );

            inputData[ dataType ].required = requiredInputData[ dataType ].required;
        }

        return inputData;
    };

    mw.calculators.objectClasses.DrugDosageCalculation.prototype.getCalculationDataValues = function() {
        var data = mw.calculators.objectClasses.AbstractCalculation.prototype.getCalculationDataValues.call( this );

        data.drug = this.drug;

        data.indication = data[ this.getVariablePrefix() + 'indication' ] !== null ?
            mw.calculators.getDrugIndication( mw.calculators.getVariable( this.getVariableIds().indication ).getValue() ) :
            null;

        delete data[ this.getVariablePrefix() + 'indication' ];

        data.preparation = data[ this.getVariablePrefix() + 'preparation' ] !== null ?
            this.drug.preparations[ mw.calculators.getVariable( this.getVariableIds().preparation ).getValue() ] :
            null;

        delete data[ this.getVariablePrefix() + 'preparation' ];

        data.route = data[ this.getVariablePrefix() + 'route' ] !== null ?
            mw.calculators.getDrugRoute( mw.calculators.getVariable( this.getVariableIds().route ).getValue() ) :
            null;

        delete data[ this.getVariablePrefix() + 'route' ];

        return data;
    };

    mw.calculators.objectClasses.DrugDosageCalculation.prototype.getClassName = function() {
        return 'DrugDosageCalculation';
    };

    mw.calculators.objectClasses.DrugDosageCalculation.prototype.getDescription = function() {
        var description = this.drug.description ? this.drug.description : '';

        if( this.activeDosageId !== null && this.drug.dosages[ this.activeDosageId ].description ) {
            description += description ? '<br/><br/>' : '';
            description += this.drug.dosages[ this.activeDosageId ].description;
        }

        return description;
    };

    mw.calculators.objectClasses.DrugDosageCalculation.prototype.getInfoButton = function( infoCount ) {
        var infoContainerId = this.getContainerId() + '-info';

        if( infoCount ) {
            infoContainerId += '-' + infoCount;
        }

        var infoString = 'More information';

        infoString += !mw.calculators.isMobile() ? ' about this dose' : '';

        return $( '<div>', {
            class: this.getElementClasses( 'infoButton' )
        } )
            .append( $( '<a>', {
                'data-toggle': 'collapse',
                href: '#' + infoContainerId,
                role: 'button',
                'aria-expanded': 'false',
                'aria-controls': infoContainerId
            } )
                .append( $( '<i>', {
                    class: 'far fa-question-circle'
                } ), '&nbsp;' + infoString ) );
    };

    mw.calculators.objectClasses.DrugDosageCalculation.prototype.getProperties = function() {
        var inheritedProperties = mw.calculators.objectClasses.AbstractCalculation.prototype.getProperties();

        return this.mergeProperties( inheritedProperties, {
            required: [
                'drug'
            ],
            optional: []
        } );
    };

    mw.calculators.objectClasses.DrugDosageCalculation.prototype.getReferences = function() {
        var references = this.drug.references;

        if( this.activeDosageId !== null && this.drug.dosages[ this.activeDosageId ].references.length ) {
            references = references
                .concat( this.drug.dosages[ this.activeDosageId ].references )
                .filter( mw.calculators.uniqueValues );
        }

        return references;
    };

    mw.calculators.objectClasses.DrugDosageCalculation.prototype.getSearchString = function() {
        return this.drug.name + ' ' + this.drug.color + ' ' + this.drug.getIndications().join( ' ' );
    };

    mw.calculators.objectClasses.DrugDosageCalculation.prototype.getTitleHtml = function() {
        var $title = $( '<a>', {
            class: this.getElementClasses( 'title-name' ),
            href: mw.util.getUrl( this.drug.name ),
            text: this.getTitleString()
        } ).css( 'background-color', '#fff' );

        var highlightColor = this.drug.color.getHighlightColor();

        if( highlightColor ) {
            var highlightContainerAttributes = {
                class: this.getElementClasses( 'title-highlight' )
            };

            var highlightContainerCss = {};

            highlightContainerCss[ 'background' ] = highlightColor;

            $title = $( '<span>', highlightContainerAttributes ).append( $title ).css( highlightContainerCss );
        }

        var primaryColor = this.drug.color.getPrimaryColor();

        if( primaryColor ) {
            var backgroundContainerAttributes = {
                class: this.getElementClasses( 'title-background' )
            };

            var backgroundContainerCss = {};

            if( this.drug.color.isStriped() ) {
                backgroundContainerCss[ 'background' ] = 'repeating-linear-gradient(135deg,rgba(0,0,0,0),rgba(0,0,0,0)10px,rgba(255,255,255,1)10px,rgba(255,255,255,1)20px),' + primaryColor;
            } else {
                backgroundContainerCss[ 'background'] = primaryColor;
            }

            $title = $( '<span>', backgroundContainerAttributes ).append( $title ).css( backgroundContainerCss );
        }

        return $title;
    };

    mw.calculators.objectClasses.DrugDosageCalculation.prototype.getTitleString = function() {
        return this.drug ? this.drug.name : '';
    }

    mw.calculators.objectClasses.DrugDosageCalculation.prototype.getVariableIds = function() {
        return {
            indication: this.getVariablePrefix() + 'indication',
            preparation: this.getVariablePrefix() + 'preparation',
            route: this.getVariablePrefix() + 'route'
        };
    };

    mw.calculators.objectClasses.DrugDosageCalculation.prototype.getVariableOptions = function( variableId ) {
        if( variableId === this.getVariablePrefix() + 'indication' ) {
            return this.drug.getIndications();
        } else if( variableId === this.getVariablePrefix() + 'preparation' ) {
            // Exclude preparations which require dilution
            return this.drug.getPreparations( true );
        } else if( variableId === this.getVariablePrefix() + 'route' ) {
            return this.drug.getRoutes();
        }
    };

    mw.calculators.objectClasses.DrugDosageCalculation.prototype.getVariablePrefix = function() {
        return this.drug.id + '-';
    };

    mw.calculators.objectClasses.DrugDosageCalculation.prototype.initialize = function() {
        if( typeof this.drug === 'string' ) {
            var drug = mw.calculators.getDrug( this.drug );

            if( !drug ) {
                throw new Error( 'DrugDosage references drug "' + this.drug + '" which is not defined' );
            }

            this.drug = drug;
        }

        this.activeDosageId = null;

        this.updateVariables();

        mw.calculators.objectClasses.AbstractCalculation.prototype.initialize.call( this );
    };

    mw.calculators.objectClasses.DrugDosageCalculation.prototype.updateVariables = function() {
        var variableIds = this.getVariableIds();

        for( var variableType in variableIds ) {
            var variableId = variableIds[ variableType ];
            var variableOptions = this.getVariableOptions( variableId );
            var variableOptionValues = {};
            var defaultOption = 0;

            for( var iVariableOption in variableOptions ) {
                var variableOption = variableOptions[ iVariableOption ];

                defaultOption = variableOption.default ? iVariableOption : defaultOption;

                variableOptionValues[ variableOption.id ] = String( variableOption );
            }

            var defaultValue = variableOptions.length ? variableOptions[ defaultOption ].id : null;

            var variable = mw.calculators.getVariable( variableId );

            if( !variable ) {
                var newVariable = {};

                // TODO put this somewhere else
                var abbreviation;

                if( variableType === 'indication' ) {
                    abbreviation = 'Use';
                } else if( variableType === 'route' ) {
                    abbreviation = 'Route';
                } else if( variableType === 'preparation' ) {
                    abbreviation = 'Prep';
                }

                newVariable[ variableId ] = {
                    name: variableType.charAt(0).toUpperCase() + variableType.slice(1),
                    abbreviation: abbreviation,
                    type: 'string',
                    defaultValue: defaultValue,
                    options: variableOptionValues
                };

                mw.calculators.addVariables( newVariable );
            } else {
                // Probably not ideal to reach into the variable to change these things directly
                // Perhaps add helper functions to variable class
                mw.calculators.variables[ variableId ].defaultValue = defaultValue;
                mw.calculators.variables[ variableId ].options = variableOptionValues;
            }
        }
    };






    mw.calculators.addDrugCalculators = function( moduleId, drugCalculatorData, className ) {
        className = className ? className : 'DrugDosageCalculator';

        for( var drugCalculatorId in drugCalculatorData ) {
            drugCalculatorData[ drugCalculatorId ].module = moduleId;

            for( var iCalculation in drugCalculatorData[ drugCalculatorId].calculations ) {
                drugCalculatorData[ drugCalculatorId].calculations[ iCalculation ] = moduleId + '-' +
                    drugCalculatorData[ drugCalculatorId].calculations[ iCalculation ];
            }
        }

        mw.calculators.addCalculators( moduleId, drugCalculatorData, className );
    };



    /**
     * Class DrugDosageCalculator
     * @param {Object} propertyValues
     * @returns {mw.calculators.objectClasses.DrugDosageCalculator}
     * @constructor
     */
    mw.calculators.objectClasses.DrugDosageCalculator = function( propertyValues ) {
        mw.calculators.objectClasses.CalculatorObject.call( this, this.getProperties(), propertyValues );
    };

    mw.calculators.objectClasses.DrugDosageCalculator.prototype = Object.create( mw.calculators.objectClasses.AbstractCalculator.prototype );

    mw.calculators.objectClasses.DrugDosageCalculator.prototype.getClassName = function() {
        return 'DrugDosageCalculator';
    };

}() );