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

From WikiAnesthesia
m (Chris.Rishel moved page MediaWiki:Gadget-calculator-drugDosages.js to MediaWiki:Gadget-calculator-drugDosage.js without leaving a redirect)
 
(40 intermediate revisions by the same user not shown)
Line 1: Line 1:
/**
* @author Chris Rishel
*/
( function() {
( function() {
    mw.calculators.getDrugDosageCalculationId = function( drugId ) {
        return 'drugDosages-' + drugId;
    };
    mw.calculators.initializeDrugDosages = function() {
        for( var drugId in mw.calculators.drugs ) {
            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();
        }
    };
     /**
     /**
     * DrugColor data
     * Class DrugDosageCalculation
    * @param {Object} propertyValues
    * @returns {mw.calculators.objectClasses.DrugDosageCalculation}
    * @constructor
     */
     */
     mw.calculators.addDrugColors( {
     mw.calculators.objectClasses.DrugDosageCalculation = function( propertyValues ) {
         anticholinergic: {
         mw.calculators.objectClasses.CalculatorObject.call( this, this.getProperties(), propertyValues );
            primaryColor: '#9fc96b'
 
        },
         this.initialize();
        benzodiazepine: {
    };
            primaryColor: '#f57921'
 
        },
    mw.calculators.objectClasses.DrugDosageCalculation.prototype = Object.create( mw.calculators.objectClasses.AbstractCalculation.prototype );
        benzodiazepineReversal: {
 
            parentColor: 'benzodiazepine',
    mw.calculators.objectClasses.DrugDosageCalculation.prototype.calculate = function( data ) {
            striped: true
         var value = {
         },
             message: null,
        cardiovascularAgonist: {
             population: null,
            primaryColor: '#e1c9df'
             preparation: data.preparation,
        },
             dose: []
        cardiovascularAntagonist: {
         };
            parentColor: 'cardiovascularAgonist',
 
            striped: true
         this.activeDosageId = null;
        },
 
        default: {
         if( !data.drug.dosages.length ) {
            primaryColor: '#fff',
             value.message = 'No dose data';
            highlightColor: '#fff'
 
        },
             return value;
         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: {
            parentColor: 'neuromuscularBlocker',
            highlightColor: '#000'
         }
         }
    } );


        // 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 = -1;


            // Make sure the indication matches
            if( drugDosage.indication.id === data.indication.id ) {
                for( var iRoute in drugDosage.routes ) {
                    // Make sure the route matches
                    if( drugDosage.routes[ iRoute ].id === data.route.id ) {
                        populationScore = drugDosage.population.getCalculationDataScore( data );


    /**
                        break;
    * DrugPopulation data
                     }
    */
    mw.calculators.addDrugPopulations( {
        general: {
            name: 'General',
        },
        general120kg: {
            name: 'General (≥120kg)',
            abbreviation: '≥120kg',
            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'
                 }
                 }
             }
             }
            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;
    * DrugRoute data
    */
    mw.calculators.addDrugRoutes( {
        ett: {
             name: 'Endotracheal',
            abbreviation: 'ETT'
        },
        iv: {
            name: 'Intravenous',
            abbreviation: 'IV',
            default: true
        },
        im: {
            name: 'Intramuscular',
            abbreviation: 'IM'
        },
        io: {
            name: 'Intraosseous',
            abbreviation: 'IO'
        },
        po: {
            name: 'Oral',
            abbreviation: 'PO'
        },
        pr: {
            name: 'Rectal',
            abbreviation: 'PR'
         }
         }
    } );


        // 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 ];


    /**
        if( !dosage.dose.length && dosage.description ) {
    * DrugIndication data
             value.message = dosage.description;
    */
 
    mw.calculators.addDrugIndications( {
             return value;
        abxProphylaxis: {
             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'
         }
         }
    } );


        // 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
    * Drug data
            value.dose[ iDose ] = {
    */
                amountPerWeight: {},
                mass: {},
                volume: {},
                weightCalculation: weightCalculation ? weightCalculation : null
            };


            if( dose.text ) {
                // Only show raw text dose
                continue;
            }


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


    /**
                 var doseValue = dose[ mathProperty ];
    * Acetaminophen
    */
    mw.calculators.addDrugs( {
        acetaminophen: {
            name: 'Acetaminophen',
            dosages: [
                {
                    indication: 'analgesia',
                    population: 'general',
                    description: 'Max dose 4 g/day. Use caution in hepatic impairment.',
                    dose: {
                        dose: '1 g'
                    }
                }, {
                    indication: 'analgesia',
                    population: 'general',
                    routes: [ 'po', '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',
                    routes: [ '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',
                    routes: [ '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',
                            frequency: 'q6h'
                        }
                    ]
                }
            ],
            preparations: [
                {
                    concentration: '10 mg/mL'
                }
            ]
        }
    } );


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


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


    /**
                        if( weightValue ) {
    * Aprepitant
                            // Amount could be either a mass or volume
    */
                            var amountBase = doseUnitsByBase.mass ? 'mass' :
    mw.calculators.addDrugs( [
                                doseUnitsByBase.volume ? 'volume' : null;
        {
            id: 'aprepitant',
            name: 'Aprepitant',
            dosages: [
                {
                    indication: 'ponv',
                    population: 'general',
                    routes: 'po',
                    dose: {
                        min: '40 mg'
                    }
                }
            ],
            preparations: [
                {
                    concentration: '40 mg/vial'
                }
            ]
        }
    ] );


                            if( amountBase ) {
                                var amountUnits = doseUnitsByBase[ amountBase ];


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


    /**
                                // For whatever reason math.format will simplify the units, but math.formatUnits will not
    * Atropine
                                // as a hack, we recreate a new unit value with the correct formatting of the result
    */
                                value.dose[ iDose ][ amountBase ][ mathProperty ] = math.unit( math.multiply( doseValue, weightValue ).format() ).to( amountUnits );
    mw.calculators.addDrugs( [
                            }
        {
                        }
            id: 'atropine',
                     } else {
            name: 'Atropine',
                         value.dose[ iDose ].mass[ mathProperty ] = doseValue;
            color: 'anticholinergic',
            description: 'Low doses may cause paradoxical bradycardia',
            dosages: [
                {
                    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
                }
            ]
        }
    ] );


                    if( data.preparation && value.dose[ iDose ].mass[ mathProperty ] ) {
                        var volumeUnits;
                        // 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
    * Calcium chloride
                        value.dose[ iDose ].volume[ mathProperty ] = math.unit( math.multiply( value.dose[ iDose ].mass[ mathProperty ], math.divide( 1, data.preparation.concentration ) ).format() ).to( volumeUnits );
    */
    mw.calculators.addDrugs( [
        {
            id: 'calciumChloride',
            name: 'Calcium chloride',
            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>',
            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: [
                {
                    indication: 'hypocalcemia',
                    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: [
 
                 {
             if( value.dose[ iDose ].mass.hasOwnProperty( 'absoluteMin' ) ) {
                     concentration: '100 mg/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.
            ]
                    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' ) ) {
    * Calcium gluconate
                        value.dose[ iDose ].volume.min = value.dose[ iDose ].volume.absoluteMin;
    */
    mw.calculators.addDrugs( [
        {
            id: 'calciumGluconate',
            name: 'Calcium gluconate',
            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: [
                '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: [
                {
                    indication: 'hypocalcemia',
                    population: 'general',
                    dose: {
                        min: '1 g',
                        max: '2 g',
                        duration: '5-10 min'
                     }
                     }
                 }, {
                 } else if( value.dose[ iDose ].mass.hasOwnProperty( 'dose' ) && math.larger( value.dose[ iDose ].mass.absoluteMin, value.dose[ iDose ].mass.dose ) ) {
                     indication: 'hypocalcemia',
                     value.dose[ iDose ].mass.dose = value.dose[ iDose ].mass.absoluteMin;
                     population: 'pediatric',
 
                    dose: {
                     if( value.dose[ iDose ].volume.hasOwnProperty( 'absoluteMin' ) ) {
                         dose: '30 mg/kg',
                         value.dose[ iDose ].volume.dose = value.dose[ iDose ].volume.absoluteMin;
                        absoluteMax: '2 g',
                        duration: '5-10 min'
                     }
                     }
                 }
                 }
             ],
             }
             preparations: [
 
                 {
             if( value.dose[ iDose ].mass.hasOwnProperty( 'absoluteMax' ) ) {
                     concentration: '100 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;
    * Cefazolin
                         delete value.dose[ iDose ].volume.max;
    */
    mw.calculators.addDrugs( {
        cefazolin: {
            name: 'Cefazolin',
            dosages: [
                {
                    indication: 'abxProphylaxis',
                    population: 'general',
                    dose: {
                         dose: '2 g',
                        frequency: 'q4h'
                     }
                     }
                 }, {
                 } else if( value.dose[ iDose ].mass.hasOwnProperty( 'max' ) && math.smaller( value.dose[ iDose ].mass.absoluteMax, value.dose[ iDose ].mass.max ) ) {
                     indication: 'abxProphylaxis',
                     value.dose[ iDose ].mass.max = value.dose[ iDose ].mass.absoluteMax;
                     population: 'general120kg',
 
                    dose: {
                     if( value.dose[ iDose ].volume.hasOwnProperty( 'absoluteMax' ) ) {
                         dose: '3 g',
                         value.dose[ iDose ].volume.max = value.dose[ iDose ].volume.absoluteMax;
                        frequency: 'q4h'
                     }
                     }
                 }, {
                 } else if( value.dose[ iDose ].mass.hasOwnProperty( 'dose' ) && math.smaller( value.dose[ iDose ].mass.absoluteMax, value.dose[ iDose ].mass.dose ) ) {
                     indication: 'abxProphylaxis',
                     value.dose[ iDose ].mass.dose = value.dose[ iDose ].mass.absoluteMax;
                     population: 'pediatric',
 
                    dose: {
                     if( value.dose[ iDose ].volume.hasOwnProperty( 'absoluteMax' ) ) {
                         dose: '30 mg/kg',
                         value.dose[ iDose ].volume.dose = value.dose[ iDose ].volume.absoluteMax;
                        absoluteMax: '2 g',
                        frequency: 'q4h'
                     }
                     }
                 }
                 }
             ],
             }
            preparations: [
                {
                    concentration: '1 g/vial'
                }
            ]
         }
         }
    } );


        return value;
    };


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


    /**
        if( !$calculationContainer.length ) {
    * Cisatracurium
             return;
    */
    mw.calculators.addDrugs( {
        cisatracurium: {
            name: 'Cisatracurium',
            color: 'neuromuscularBlocker',
            dosages: [
                {
                    indication: 'neuromuscularBlockade',
                    population: 'general',
                    dose: {
                        dose: '0.2 mg/kg'
                    }
                }
            ],
            preparations: [
                {
                    concentration: '2 mg/mL'
                }, {
                    concentration: '20 mg/mL'
                }
             ]
         }
         }
    } );


        // 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 ) {
    * Dantrolene
                var elementType = elementTypes[ iElementType ];
    */
 
    mw.calculators.addDrugs( {
                // If an input contained by $container has user input focus, $container will not rerender (would be
        dantrolene: {
                // annoying behavior to the user). However, if it contains subelements which should try to rerender,
            name: 'Dantrolene',
                // add those elements to the contains property.
            dosages: [
                 elements[ elementType ] = {
                 {
                     $container: null,
                     indication: 'malignantHyperthermia',
                     contains: [],
                     population: 'general',
                     id: calculation.getContainerId() + '-' + elementType
                     dose: {
                };
                        dose: '2.5 mg/kg'
 
                    },
                if( calculationCount ) {
                     description: 'Repeat up to a cumulative dose of 10 mg/kg'
                     elements[ elementType ].id += '-' + calculationCount;
                 }
                 }
             ],
             }
             preparations: [
 
                {
             // Create title element and append to container
                    concentration: '250 mg/vial'
            elements.title.$container = $( '<div>', {
                 }, {
                 id: elements.title.id,
                    concentration: '20 mg/vial'
                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
    * Dexmedetomidine
             var $dose = $( '<div>', {
    */
                id: calculation.getContainerId() + '-dose',
    mw.calculators.addDrugs( {
                 class: 'col-7 ' + calculation.getElementClasses( 'dose' )
        dexmedetomidine: {
             } );
            name: 'Dexmedetomidine',
            color: 'sedativeHypnotic',
            searchData: 'dexdor precedex',
             dosages: [
                {
                    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
                }
             ]
        }
    } );


            var dash = '-';


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


    /**
            if( !calculation.value ) {
    * Dexamethasone
                 $dose.append( $( '<i>' ).append( 'Error calculating dose' ) );
    */
            } else if( calculation.activeDosageId === null ) {
    mw.calculators.addDrugs( [
                 if( calculation.value.message ) {
        {
                     $dose.append( $( '<i>' ).append( calculation.value.message ) );
            id: 'dexamethasone',
            name: 'Dexamethasone',
            dosages: [
                 {
                    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: [
                {
                    concentration: '2 mg/mL'
                 }
                 }
             ]
             } else {
        }
                var dosage = calculation.drug.dosages[ calculation.activeDosageId ];
    ] );


                if( dosage.population && dosage.population.id !== mw.calculators.getOptionValue( 'defaultDrugPopulation' ) ) {
                    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 );
    * Dobutamine
    */
    mw.calculators.addDrugs( {
        dobutamine: {
            name: 'Dobutamine',
            color: 'cardiovascularAgonist',
            description: 'First line vasopressor for cardiogenic shock with low cardiac output and maintained blood pressure',
            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: {
                        min: '0.5 mcg/kg/min',
                        max: '20 mcg/kg/min'
                    }
                }
            ],
            preparations: [
                {
                    concentration: '0.5 mg/mL'
                }, {
                    concentration: '12.5 mg/mL'
                 }
                 }
            ]
        }
    } );


                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 ) {
    * Dopamine
                         $doseData.append( dose.name + '<br />' );
    */
    mw.calculators.addDrugs( {
        dopamine: {
            name: 'Dopamine',
            color: 'cardiovascularAgonist',
            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>',
            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.',
                        '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: {
                        min: '2 mcg/kg/min',
                        max: '50 mcg/kg/min'
                     }
                     }
                }, {
 
                    indication: 'hypotension',
                     if( dose.text ) {
                    population: 'pediatric',
                         // Only show text
                     dose: {
                         $doseData.append( dose.text + '<br />' );
                         min: '5 mcg/kg/min',
 
                         max: '20 mcg/kg/min'
                        continue;
                     }
                     }
                }
            ],
            preparations: [
                {
                    concentration: '1.6 mg/mL'
                }, {
                    concentration: '40 mg/mL'
                }
            ]
        }
    } );


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


                    var administration = '';
                    var administrationDisplayed = false;


    /**
                    if( dosage.routes && !mw.calculators.isMobile() ) {
    * Ephedrine
                         administration += dosage.getRouteString();
    */
    mw.calculators.addDrugs( {
        ephedrine: {
            name: 'Ephedrine',
            color: 'cardiovascularAgonist',
            dosages: [
                {
                    indication: 'hypotension',
                    population: 'general',
                    dose: {
                         min: '2.5 mg',
                        max: '25 mg'
                     }
                     }
                }, {
 
                     indication: 'hypotension',
                     var doseAdministration = dose.getAdministration();
                    population: 'general',
 
                     routes: [ 'im' ],
                     if( doseAdministration ) {
                    dose: {
                         administration += administration ? ' ' : '';
                         min: '25 mg',
                         administration += doseAdministration;
                         max: '50 mg'
                     }
                     }
                }, {
 
                     indication: 'hypotension',
                     var amountPerWeightHtml = '';
                     population: 'pediatric',
 
                     dose: {
                     if( doseValue.amountPerWeight.hasOwnProperty( 'dose' ) ) {
                         min: '0.02 mg/kg',
                        amountPerWeightHtml += mw.calculators.getValueString( doseValue.amountPerWeight.dose );
                         max: '0.2 mg/kg'
                     } else if( doseValue.amountPerWeight.hasOwnProperty( 'min' ) &&
                        doseValue.amountPerWeight.hasOwnProperty( 'max' ) ) {
 
                        // getValueString will simplify the value and may adjust the units
                         var amountPerWeightMinValue = math.unit( mw.calculators.getValueString( doseValue.amountPerWeight.min ) );
                        var amountPerWeightMaxValue = math.unit( mw.calculators.getValueString( doseValue.amountPerWeight.max ) );
 
                        if( amountPerWeightMinValue.formatUnits() !== amountPerWeightMaxValue.formatUnits() ) {
                            // If the units between min and max don't match, show both
                            amountPerWeightHtml += mw.calculators.getValueString( amountPerWeightMinValue );
                        } else {
                            amountPerWeightHtml += mw.calculators.getValueNumber( amountPerWeightMinValue );
                        }
 
                        amountPerWeightHtml += dash;
                         amountPerWeightHtml += mw.calculators.getValueString( amountPerWeightMaxValue );
                     }
                     }
                }
            ],
            preparations: [
                {
                    concentration: '5 mg/mL'
                }, {
                    concentration: '50 mg/mL'
                }
            ]
        }
    } );


                    if( amountPerWeightHtml ) {
                        if( administration && ! administrationDisplayed ) {
                            amountPerWeightHtml += ' ' + administration;
                            administrationDisplayed = true;
                        }
                        var amountPerWeightNotesHtml = '';
                        if( doseValue.mass.hasOwnProperty( 'absoluteMin' ) ) {
                            amountPerWeightNotesHtml += 'Min: ' + mw.calculators.getValueString( doseValue.mass.absoluteMin );
                        } else if( doseValue.mass.hasOwnProperty( 'absoluteMax' ) ) {
                            amountPerWeightNotesHtml += 'Max: ' + mw.calculators.getValueString( doseValue.mass.absoluteMax );
                        }


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


    /**
                            amountPerWeightNotesHtml += dose.weightCalculation[ 0 ].getTitleString();
    * Epinephrine
    */
    mw.calculators.addDrugs( {
        epinephrine: {
            name: 'Epinephrine',
            color: 'epinephrine',
            dosages: [
                {
                    indication: 'hypotension',
                    population: 'general',
                    description: 'First line vasopressor for anaphylactic shock',
                    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',
                            frequency: 'q1-5m'
                        }, {
                            name: 'Infusion',
                            min: '0.01 mcg/kg/min',
                            max: '2 mcg/kg/min'
                         }
                         }
                    ]
 
                }, {
                         if( amountPerWeightNotesHtml ) {
                    indication: 'hypotension',
                             amountPerWeightHtml += ' (' + amountPerWeightNotesHtml + ')';
                    population: 'pediatric',
                    description: 'First line vasopressor for anaphylactic shock',
                    references: [
                        '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.',
                         '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.'
                    ],
                    dose: [
                        {
                             name: 'Bolus',
                            min: '1 mcg/kg',
                            max: '5 mcg/kg',
                            frequency: 'q1-5m'
                        }, {
                            name: 'Infusion',
                            min: '0.02 mcg/kg/min',
                            max: '1 mcg/kg/min'
                         }
                         }
                    ]
 
                }, {
                        amountPerWeightHtml = $( '<li>' ).append( amountPerWeightHtml );
                    indication: 'acls',
 
                    population: 'general',
                         $doseList.append( amountPerWeightHtml );
                    routes: [ 'iv', 'io' ],
                    dose: {
                        dose: '1 mg',
                         frequency: 'q3-5m'
                     }
                     }
                }, {
 
                    indication: 'acls',
                    var weightCalculationInfo = '';
                    population: 'general',
                    var weightCalculationInfoDisplayed = false;
                    routes: [ 'ett' ],
 
                    description: 'Dilute in 10 mL before administering',
                    if( doseValue.weightCalculation && dose.weightCalculation.length && doseValue.weightCalculation.id !== dose.weightCalculation[ 0 ].id ) {
                    dose: {
                        var weightCalculationLabel = doseValue.weightCalculation.getTitleString();
                        min: '2 mg',
 
                        max: '2.5 mg',
                        var $weightCalculationInfoIcon = $( '<a>', {
                        frequency: 'q3-5m'
                            tabindex: '0',
                            'data-container': 'body',
                            'data-html': 'true',
                            'data-placement': 'top',
                            'data-toggle': 'popover',
                            'data-trigger': 'focus',
                            'data-content': doseValue.weightCalculation.name +
                                ' is being used because data is missing for ' +
                                dose.weightCalculation[ 0 ].name + ': ' + dose.weightCalculation[ 0 ].message
                        } )
                            .append( $( '<i>', {
                                class: 'far fa-question-circle'
                            } ) );
 
                        weightCalculationInfo = '&nbsp; (' + weightCalculationLabel + '&nbsp;' + $weightCalculationInfoIcon[ 0 ].outerHTML + ')';
                     }
                     }
                }, {
 
                     indication: 'acls',
                     var massHtml = '';
                     population: 'pediatric',
 
                     routes: [ 'iv', 'io' ],
                     if( doseValue.mass.hasOwnProperty( 'dose' ) ) {
                    dose: {
                        massHtml += mw.calculators.getValueString( doseValue.mass.dose );
                        dose: '10 mcg/kg',
                     } else if( doseValue.mass.hasOwnProperty( 'min' ) &&
                         absoluteMax: '1 mg',
                        doseValue.mass.hasOwnProperty( 'max' ) ) {
                         frequency: 'q3-5m'
 
                        // 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 );
                     }
                     }
                }, {
 
                     indication: 'anaphylaxis',
                     if( massHtml ) {
                    population: 'general',
                         if( administration && ! administrationDisplayed ) {
                    dose: [
                             massHtml += ' ' + administration;
                         {
                             administrationDisplayed = true;
                             name: 'Bolus',
                        }
                             dose: '10 mcg/kg',
 
                            absoluteMax: '0.5 mg',
                         if( weightCalculationInfo && !weightCalculationInfoDisplayed ) {
                            frequency: 'q3-5m'
                             massHtml += weightCalculationInfo;
                         }, {
                             weightCalculationInfoDisplayed = true;
                             name: 'Infusion',
                             min: '0.1 mcg/kg/min',
                            max: '1 mcg/kg/min'
                         }
                         }
                    ]
 
                }, {
                         massHtml = $( '<li>' ).append( massHtml );
                    indication: 'anaphylaxis',
 
                    population: 'general',
                         $doseList.append( massHtml );
                    routes: [ 'im' ],
                    dose: {
                         dose: '10 mcg/kg',
                        frequency: 'q5-15m',
                         absoluteMax: '0.5 mg'
                     }
                     }
                }
            ],
            preparations: [
                {
                    concentration: '10 mcg/mL'
                }, {
                    concentration: '16 mcg/mL'
                }, {
                    concentration: '100 mcg/mL',
                    default: true
                }, {
                    concentration: '1 mg/mL'
                }
            ]
        }
    } );


                    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;
    * Etomidate
                        volumeHtml += mw.calculators.getValueString( doseValue.volume.max );
    */
    mw.calculators.addDrugs( {
        etomidate: {
            name: 'Etomidate',
            color: 'sedativeHypnotic',
            dosages: [
                {
                    indication: 'generalAnesthesia',
                    population: 'general',
                    dose: {
                        min: '0.2 mg/kg',
                        max: '0.6 mg/kg',
                        weightCalculation: [ 'lbw', 'ibw' ]
                     }
                     }
                }
            ],
            preparations: [
                {
                    concentration: '2 mg/mL'
                }
            ]
        }
    } );


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


                        if( weightCalculationInfo && !weightCalculationInfoDisplayed ) {
                            volumeHtml += weightCalculationInfo;
                            weightCalculationInfoDisplayed = true;
                        }


    /**
                        if( calculation.value.preparation && !mw.calculators.isMobile() ) {
    * Ketamine
                             volumeHtml += ' of ' + String( calculation.value.preparation );
    */
    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'  ]
                    }
                }, {
                    indication: 'generalAnesthesia',
                    population: 'general',
                    routes: [ 'im' ],
                    dose: {
                        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'
                }, {
                    concentration: '50 mg/mL'
                }, {
                    concentration: '100 mg/mL'
                }
            ]
        }
    } );


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


                        $doseList.append( volumeHtml );


    /**
                         hasVolume = true;
    * Ketorolac
    */
    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'
                     }
                     }
                    $doseData.append( $doseList );
                 }
                 }
            ],
 
            preparations: [
                 $dose.append( $doseData );
                 {
 
                    concentration: '15 mg/mL'
                 if( calculation.hasInfo() ) {
                 }, {
                     $dose.append( calculation.getInfoButton( calculationCount ) );
                     concentration: '30 mg/mL',
                    default: true
                 }
                 }
             ]
             }
        }
 
    ] );
 
            // 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';
            }
 
            if( mw.calculators.getOptionValue( 'patientinputinline' ) ) {
                // If patient input should be inline, add patient input group
                $options.append( mw.calculators.createInputGroup( [
                    'weight',
                    'height',
                    'age',
                    'gender'
                ], true, 4 ) );
            }
 
            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
    * Lidocaine
                        .append( $( '<div>', {
    */
                            class: optionsRowClass
    mw.calculators.addDrugs( {
                        } )
        lidocaine: {
                            .append(
            name: 'Lidocaine',
                                $( '<div>', {
            color: 'localAnesthetic',
                                    class: optionLabelClass,
            dosages: [
                                    html: preparationVariable.getLabelString() + '&nbsp;'
                {
                                } ),
                    indication: 'intubation',
                                $( '<div>', {
                    population: 'general',
                                    class: optionValueClass
                    dose: {
                                } )
                        dose: '1 mg/kg'
                                    .append( preparationVariable.createInput({
                    }
                                        class: 'calculator-container-input-DrugDosageCalculator-options',
                                        hideLabel: true,
                                        inline: true
                                    } ) ) ) );
                 }
                 }
             ],
             }
             preparations: [
 
                {
            elements.dosage.$container.append( $dose, $options );
                    concentration: '1 pct'
 
                }, {
            // Add elements to the contains array
                    concentration: '2 pct'
             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 ) {
    * Midazolam
                                if( !$.contains( $existingContainedContainer[ 0 ], $( ':focus' )[ 0 ] ) ) {
    */
                                    $existingContainedContainer.replaceWith( $containedElement );
    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',
                    population: 'general',
                    routes: [ 'im' ],
                    dose: {
                        min: '0.07 mg/kg',
                        max: '0.08 mg/kg'
                    }
                }, {
                    indication: 'anxiolysis',
                    population: 'general',
                    routes: [ 'po' ],
                    dose: {
                        dose: '0.5 mg/kg',
                         absoluteMax: '20 mg'
                    }
                }, {
                    indication: 'generalAnesthesia',
                    population: 'general',
                    dose: {
                        min: '0.1 mg/kg',
                        max: '0.3 mg/kg',
                        weightCalculation: [ 'lbw', 'ibw' ]
                     }
                     }
                } else {
                    $( this ).append( elements[ elementType ].$container );
                 }
                 }
             ],
             }
             preparations: [
 
                {
             calculationCount++;
                    concentration: '1 mg/mL'
        } );
                }, {
 
                    concentration: '5 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 ) {
    * Milrinone
                    requiredInputData[ dataType ].required = inputData[ dataType ].required;
    */
                 } else {
    mw.calculators.addDrugs( {
                     // Data is only truly required if it is required by all dosage calculations, so use array intersection
        milrinone: {
                     requiredInputData[ dataType ].required = requiredInputData[ dataType ].required.filter( function( index ) {
            name: 'Milrinone',
                         return dosageInputData[ dataType ].required.indexOf( index ) !== -1;
            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: [
 
                {
             initializeRequiredData = false;
                    concentration: '0.2 mg/mL'
                }, {
                    concentration: '1 mg/mL'
                }
             ]
         }
         }
    } );


        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;
    * 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'
                }
            ]
         }
         }
    } );


        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 = '';


    /**
         if( this.activeDosageId !== null && this.drug.dosages[ this.activeDosageId ].description ) {
    * Norepinephrine
            description += description ? '<br/><br/>' : '';
    */
             description += this.drug.dosages[ this.activeDosageId ].description;
    mw.calculators.addDrugs( {
         norepinephrine: {
            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'
                        }
                    ]
                }, {
                    indication: 'hypotension',
                    population: 'pediatric',
                    dose: [
                        {
                            name: 'Bolus',
                            min: '0.05 mcg/kg',
                            max: '0.1 mcg/kg'
                        }, {
                            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
                }
            ]
         }
         }
    } );


        description += this.drug.description ? this.drug.description : '';
        return description;
    };
    mw.calculators.objectClasses.DrugDosageCalculation.prototype.getFormula = function() {
        return this.drug.formula;
    };


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


    /**
        if( infoCount ) {
    * Ondansetron
             infoContainerId += '-' + infoCount;
    */
    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'
                    }
                }
            ],
            preparations: [
                {
                    concentration: '2 mg/mL'
                }
            ]
         }
         }
    } );


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


        return $( '<div>', {
            class: this.getElementClasses( 'infoButton' )
        } )
            .append( $( '<a>', {
                class: 'btn btn-outline-primary btn-sm',
                'data-toggle': 'collapse',
                href: '#' + infoContainerId,
                role: 'button',
                'aria-expanded': 'false',
                'aria-controls': infoContainerId,
                html: infoString
            } ) );
    };


    /**
     mw.calculators.objectClasses.DrugDosageCalculation.prototype.getProperties = function() {
    * Phenylephrine
         var inheritedProperties = mw.calculators.objectClasses.AbstractCalculation.prototype.getProperties();
    */
 
     mw.calculators.addDrugs( {
        return this.mergeProperties( inheritedProperties, {
         phenylephrine: {
             required: [
            name: 'Phenylephrine',
                 'drug'
            color: 'cardiovascularAgonist',
             dosages: [
                 {
                    indication: 'hypotension',
                    population: 'general',
                    dose: [
                        {
                            name: 'Bolus',
                            min: '50 mcg',
                            max: '200 mcg'
                        }, {
                            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: [
             optional: []
                {
        } );
                    concentration: '10 mcg/mL'
    };
                }, {
 
                    concentration: '100 mcg/mL',
    mw.calculators.objectClasses.DrugDosageCalculation.prototype.getReferences = function() {
                    default: true
        var references = this.drug.references;
                }, {
 
                    concentration: '160 mcg/mL'
        if( this.activeDosageId !== null && this.drug.dosages[ this.activeDosageId ].references.length ) {
                 }, {
            references = references
                    concentration: '10 mg/mL',
                 .concat( this.drug.dosages[ this.activeDosageId ].references )
                    dilutionRequired: true
                 .filter( mw.calculators.uniqueValues );
                 }
            ]
         }
         }
    } );


        return references;
    };
    mw.calculators.objectClasses.DrugDosageCalculation.prototype.getSearchString = function() {
        var searchString = this.drug.name + ' ' + this.drug.color.id;
        searchString += this.drug.tradeNames.length ? ' ' + this.drug.tradeNames.join( ' ' ) : '';
        var indications = this.drug.getIndications();


        for( var iIndication in indications ) {
            var indication = indications[ iIndication ];


    /**
             searchString += ' ' + indication.getSearchString();
    * Propofol
    */
    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'
                    }
                }
            ],
            preparations: [
                {
                    concentration: '10 mg/mL'
                }
            ]
         }
         }
    } );


        searchString += this.drug.searchData ? ' ' + this.drug.searchData : '';
        return searchString.trim();
    };
    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 );
    * Remifentanil
    */
    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: [
                {
                    concentration: '50 mcg/mL'
                }, {
                    concentration: '1 mg/vial',
                    dilutionRequired: true
                }, {
                    concentration: '2 mg/vial',
                    dilutionRequired: true
                }
            ]
         }
         }
    } );


        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',
                    routes: [ '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 ) {
                throw new Error( 'DrugDosage references drug "' + this.drug + '" which is not defined' );
            }


    /**
             this.drug = drug;
    * Sufentanil
    */
    mw.calculators.addDrugs( {
        sufentanil: {
             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 ) {
    * Sugammadex
                var variableOption = variableOptions[ iVariableOption ];
    */
 
    mw.calculators.addDrugs( {
                 defaultOption = variableOption.default ? iVariableOption : defaultOption;
        sugammadex: {
            name: 'Sugammadex',
            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: [
                {
                    concentration: '100 mg/mL'
                }
            ]
        }
    } );


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


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


    /**
            var variable = mw.calculators.getVariable( variableId );
    * Vasopressin
    */
    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'
                }
            ]
        }
    } );


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


                // TODO put this somewhere else
                var abbreviation;


    /**
                if( variableType === 'indication' ) {
    * Vecuronium
                     abbreviation = 'Use';
    */
                } else if( variableType === 'route' ) {
    mw.calculators.addDrugs( {
                     abbreviation = 'Route';
        vecuronium: {
                } else if( variableType === 'preparation' ) {
            name: 'Vecuronium',
                    abbreviation = 'Prep';
            color: 'neuromuscularBlocker',
            dosages: [
                {
                     indication: 'neuromuscularBlockade',
                    population: 'general',
                     dose: [
                        {
                            name: 'Standard',
                            dose: '0.1 mg/kg'
                        }, {
                            name: 'Rapid sequence',
                            dose: '1.2 mg/kg'
                        }
                    ]
                 }
                 }
            ],
 
            preparations: [
                newVariable[ variableId ] = {
                {
                    name: variableType.charAt(0).toUpperCase() + variableType.slice(1),
                     concentration: '10 mg/vial'
                    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.hook( 'calculators.initialized' ).add( mw.calculators.initializeDrugDosages );
}() );
}() );

Latest revision as of 16:24, 5 April 2022

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

    mw.calculators.initializeDrugDosages = function() {
        for( var drugId in mw.calculators.drugs ) {
            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();
        }
    };

    /**
     * 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 = -1;

            // Make sure the indication matches
            if( drugDosage.indication.id === data.indication.id ) {
                for( var iRoute in drugDosage.routes ) {
                    // Make sure the route matches
                    if( drugDosage.routes[ iRoute ].id === data.route.id ) {
                        populationScore = drugDosage.population.getCalculationDataScore( data );

                        break;
                    }
                }
            }

            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 ];

        if( !dosage.dose.length && dosage.description ) {
            value.message = dosage.description;

            return value;
        }

        // 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 ] = {
                amountPerWeight: {},
                mass: {},
                volume: {},
                weightCalculation: weightCalculation ? weightCalculation : null
            };

            if( dose.text ) {
                // Only show raw text dose
                continue;
            }

            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 ].amountPerWeight[ mathProperty ] = doseValue;

                        if( weightValue ) {
                            // Amount could be either a mass or volume
                            var amountBase = doseUnitsByBase.mass ? 'mass' :
                                doseUnitsByBase.volume ? 'volume' : null;

                            if( amountBase ) {
                                var amountUnits = doseUnitsByBase[ amountBase ];

                                if( doseUnitsByBase.hasOwnProperty( 'time' ) ) {
                                    amountUnits += '/' + 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 ][ amountBase ][ mathProperty ] = math.unit( math.multiply( doseValue, weightValue ).format() ).to( amountUnits );
                            }
                        }
                    } else {
                        value.dose[ iDose ].mass[ mathProperty ] = doseValue;
                    }

                    if( data.preparation && value.dose[ iDose ].mass[ mathProperty ] ) {
                        var volumeUnits;

                        // 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 ) {
                $dose.append( $( '<i>' ).append( 'Error calculating dose' ) );
            } else if( calculation.activeDosageId === null ) {
                if( calculation.value.message ) {
                    $dose.append( $( '<i>' ).append( calculation.value.message ) );
                }
            } else {
                var dosage = calculation.drug.dosages[ calculation.activeDosageId ];

                if( dosage.population && dosage.population.id !== mw.calculators.getOptionValue( 'defaultDrugPopulation' ) ) {
                    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 />' );
                    }

                    if( dose.text ) {
                        // Only show text
                        $doseData.append( dose.text + '<br />' );

                        continue;
                    }

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

                    var administration = '';
                    var administrationDisplayed = false;

                    if( dosage.routes && !mw.calculators.isMobile() ) {
                        administration += dosage.getRouteString();
                    }

                    var doseAdministration = dose.getAdministration();

                    if( doseAdministration ) {
                        administration += administration ? ' ' : '';
                        administration += doseAdministration;
                    }

                    var amountPerWeightHtml = '';

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

                        // getValueString will simplify the value and may adjust the units
                        var amountPerWeightMinValue = math.unit( mw.calculators.getValueString( doseValue.amountPerWeight.min ) );
                        var amountPerWeightMaxValue = math.unit( mw.calculators.getValueString( doseValue.amountPerWeight.max ) );

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

                        amountPerWeightHtml += dash;
                        amountPerWeightHtml += mw.calculators.getValueString( amountPerWeightMaxValue );
                    }

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

                        var amountPerWeightNotesHtml = '';

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

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

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

                        if( amountPerWeightNotesHtml ) {
                            amountPerWeightHtml += ' (' + amountPerWeightNotesHtml + ')';
                        }

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

                        $doseList.append( amountPerWeightHtml );
                    }

                    var weightCalculationInfo = '';
                    var weightCalculationInfoDisplayed = false;

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

                        var $weightCalculationInfoIcon = $( '<a>', {
                            tabindex: '0',
                            'data-container': 'body',
                            'data-html': 'true',
                            'data-placement': 'top',
                            'data-toggle': 'popover',
                            'data-trigger': 'focus',
                            'data-content': doseValue.weightCalculation.name +
                                ' is being used because data is missing for ' +
                                dose.weightCalculation[ 0 ].name + ': ' + dose.weightCalculation[ 0 ].message
                        } )
                            .append( $( '<i>', {
                                class: 'far fa-question-circle'
                            } ) );

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

                    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( weightCalculationInfo && !weightCalculationInfoDisplayed ) {
                            massHtml += weightCalculationInfo;
                            weightCalculationInfoDisplayed = true;
                        }

                        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;
                        }

                        if( weightCalculationInfo && !weightCalculationInfoDisplayed ) {
                            volumeHtml += weightCalculationInfo;
                            weightCalculationInfoDisplayed = true;
                        }

                        if( calculation.value.preparation && !mw.calculators.isMobile() ) {
                            volumeHtml += ' of ' + String( calculation.value.preparation );
                        }

                        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';
            }

            if( mw.calculators.getOptionValue( 'patientinputinline' ) ) {
                // If patient input should be inline, add patient input group
                $options.append( mw.calculators.createInputGroup( [
                    'weight',
                    'height',
                    'age',
                    'gender'
                ], true, 4 ) );
            }

            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 = '';

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

        description += this.drug.description ? this.drug.description : '';

        return description;
    };

    mw.calculators.objectClasses.DrugDosageCalculation.prototype.getFormula = function() {
        return this.drug.formula;
    };

    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>', {
                class: 'btn btn-outline-primary btn-sm',
                'data-toggle': 'collapse',
                href: '#' + infoContainerId,
                role: 'button',
                'aria-expanded': 'false',
                'aria-controls': infoContainerId,
                html: 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() {
        var searchString = this.drug.name + ' ' + this.drug.color.id;

        searchString += this.drug.tradeNames.length ? ' ' + this.drug.tradeNames.join( ' ' ) : '';

        var indications = this.drug.getIndications();

        for( var iIndication in indications ) {
            var indication = indications[ iIndication ];

            searchString += ' ' + indication.getSearchString();
        }

        searchString += this.drug.searchData ? ' ' + this.drug.searchData : '';

        return searchString.trim();
    };

    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.hook( 'calculators.initialized' ).add( mw.calculators.initializeDrugDosages );
}() );