<commit_before>DDH.conversions = DDH.conversions || {};

(function(DDH) {
    "use strict";

    var localDOMInitialized = false;
    var initialized = false;

    // UI: the input / output fields
    var $convert_left,
        $convert_right,
        $select_right,
        $select_left,
        $unitSelector,
        $selects;

    var Converter = {

        // the local vars
        leftUnit:   "",
        rightUnit:  "",
        leftValue:  "",
        rightValue: "",

        setValues: function() {
            this.setLeftUnit();
            this.setRightUnit();
            this.setLeftValue();
            this.setRightValue();
        },

        setLeftUnit: function() {
            this.leftUnit = $select_left.val();
        },

        setLeftValue: function() {
            this.leftValue = $convert_left.val();
        },

        setRightUnit: function() {
            this.rightUnit = $select_right.val();
        },

        setRightValue: function() {
            this.rightValue = $convert_right.val();
        },

        eval: function( expression ) {
            return math.eval(expression).format({ precision: 6 }).split(" ")[0];
        },

        convert: function( side ) {

            this.setValues();
            if(side === "right") {
                var expression = this.leftValue + " " + this.leftUnit + " to " + this.rightUnit;
                $convert_right.val(this.eval(expression));
            } else {
                var expression = this.rightValue + " " + this.rightUnit + " to " + this.leftUnit;
                $convert_left.val(this.eval(expression));
            }
        },

        emptySelects: function() {
            // removes all the options
            $select_left.empty();
            $select_right.empty();
        },

        updateUnitSelectors: function( key ) {

            // resets the selects state
            this.emptySelects();
            // sort the keys alphabetically
            Units[key].units.sort();

            // adds the new conversion units to the selects
            for(var i = 0 ; i < Units[key].units.length ; i++) {
                $selects.append(
                    '<option value="' + Units[key].units[i].symbol + '">'
                    + Units[key].units[i].name
                    + '</option>'
                );
            }

            // set defaults. these should match Units[key].units[i].symbol
            $select_left.val(Units[key].defaults[0]);
            $select_right.val(Units[key].defaults[1]);
        },

        updateBaseUnitSelector: function( startBase ) {
            // adds the different unit types to the selector
            var unitKeys = Object.keys(Units);
            $.each(unitKeys.sort(), function(_key, value) {
                $unitSelector.append(
                    '<option value="'+value+'"' + (value === startBase ? " selected='selected'" : "") + '>'
                    + Units[value].name
                    + '</option>'
                );
            });
        }
    } // Converter

    var Utils = {

        // custom unit support
        setUpCustomUnits: function() {

            // CUSTOM ENERGY UNITS
            math.createUnit('kilojoule', '1000 joules');
            math.createUnit('gramcalorie', '4.184 joules');
            math.createUnit('kilocalorie', '4184 joules');

            // CUSTOM DIGITAL UNITS
            math.createUnit('kb', '1000 b');
            math.createUnit('mb', '1000000 b');
            math.createUnit('gb', '1000000000 b');
            math.createUnit('Tb', '1000000000000 b');
            math.createUnit('KB', '1000 B');
            math.createUnit('MB', '1000000 B');
            math.createUnit('GB', '1000000000 B');
            math.createUnit('TB', '1000 GB');
            math.createUnit('PB', '1000 TB');

            // math.createUnit('')

        },

        // caches the local DOM vars
        setUpLocalDOM: function() {
            var $root           = DDH.getDOM('conversions');
            $convert_left       = $root.find(".frm__input--left");
            $convert_right      = $root.find(".frm__input--right");
            $selects            = $root.find(".frm--top select");
            $select_right       = $root.find(".frm__select--right");
            $select_left        = $root.find(".frm__select--left");
            $unitSelector       = $root.find(".frm__select--bottom");
            localDOMInitialized = true;
        },
    } // Utils

    var Units = {
        length: {
            name: "Length",
            units: [
                { symbol: 'decameter',  name: 'Decameter' },
                { symbol: 'millimeter', name: 'Millimeter' },
                { symbol: 'micrometer', name: 'Micrometer' },
                { symbol: 'micrometer', name: 'Micrometer' },
                { symbol: 'nanometer',  name: 'Nanometer' },
                { symbol: 'picometer',  name: 'Picometer' },
                { symbol: 'kilometer',  name: 'Kilometer' },
                { symbol: 'meter',      name: 'Meter' },
                { symbol: 'cm',         name: 'Centimeter' },
                { symbol: 'hectometer', name: 'Hectometer' },
                { symbol: 'chains',     name: 'Chains' },
                { symbol: 'inch',       name: 'Inch' },
                { symbol: 'foot',       name: 'Feet' },
                { symbol: 'yard',       name: 'Yard' },
                { symbol: 'mile',       name: 'Mile' },
                { symbol: 'link',       name: 'Link' },
                { symbol: 'rod',        name: 'Rod' },
                { symbol: 'angstrom',   name: 'Angstrom' },
                { symbol: 'mil',        name: 'Mil'}
            ],
            defaults: ['meter', 'cm']
        },
        area: {
            name: "Area",
            units: [
                { symbol: 'm2',         name: 'Square Meter' },
                { symbol: 'sqin',       name: 'Square Inch' },
                { symbol: 'sqft',       name: 'Square Feet' },
                { symbol: 'sqyd',       name: 'Square Yard' },
                { symbol: 'sqmi',       name: 'Square Mile' },
                { symbol: 'acre',       name: 'Acre' },
                { symbol: 'hectare',    name: 'Hectare' }
            ],
            defaults: ['m2', 'sqin']
        },
        volume: {
            name: "Volume",
            units: [
                { symbol: 'litre',      name: 'Litre' },
                { symbol: 'millilitre', name: 'Millilitre' },
                { symbol: 'hectolitre', name: 'Hectolitre' },
                { symbol: 'decalitre',  name: 'Decalitre' },
                { symbol: 'deciliter',  name: 'Deciliter' },
                { symbol: 'centilitre', name: 'Centilitre' },
                { symbol: 'cc',         name: 'CC' },
                { symbol: 'cuin',       name: 'Cuin' },
                { symbol: 'cuft',       name: 'Cuft' },
                { symbol: 'cups',       name: 'Cups' },
                { symbol: 'cuyd',       name: 'Cubic Yard' },
                { symbol: 'pints',      name: 'Pints' },
                { symbol: 'teaspoon',   name: 'Teaspoon' },
                { symbol: 'tablespoon', name: 'Tablespoon' },
            ],
            defaults: ['litre', 'millilitre']
        },
        liquid_volume: { // double check this works
            name: "Liquid Volume",
            units: [
                { symbol: 'minim',          name: 'Minim' }, // add in proper name
                { symbol: 'fluiddram',      name: 'Fluid Dram' },
                { symbol: 'fluidounce',     name: 'Fluid Ounce' },
                { symbol: 'gill',           name: 'Gill' },
                { symbol: 'cup',            name: 'Cup' },
                { symbol: 'pint',           name: 'Pint'},
                { symbol: 'quart',          name: 'Quart'},
                { symbol: 'gallon',         name: 'Gallon'},
                { symbol: 'beerbarrel',     name: 'Beerbarrel'},
                { symbol: 'oilbarrel',      name: 'oilbarrel'},
                { symbol: 'hogshead',       name: 'Hogshead'},
                { symbol: 'drop',           name: 'Drop'},
            ],
            defaults: ['minim', 'fluiddram']
        },
        angle: {
            name: "Angle",
            units: [
                { symbol: 'rad',        name: 'Radians' },
                { symbol: 'deg',        name: 'Degrees' },
                { symbol: 'grad',       name: 'Gradians' },
                { symbol: 'cycle',      name: 'Cycles' },
                { symbol: 'arcsec',     name: 'Arcsecs' }, // update to proper name
                { symbol: 'arcmin',     name: 'Arcmin' }, // update to proper name
                { symbol: 'millirad',   name: 'Milliradian' },
            ],
            defaults: ['deg', 'rad']
        },
        duration: {
            name: "Duration",
            units: [
                { symbol: 'nanosecond',     name: 'Nanoseconds' },
                { symbol: 'microsecond',    name: 'Microseconds' },
                { symbol: 'millisecond',    name: 'Milliseconds' },
                { symbol: 'second',         name: 'Seconds' },
                { symbol: 'minute',         name: 'Minutes' },
                { symbol: 'hour',           name: 'Hours' },
                { symbol: 'day',            name: 'Days' },
                { symbol: 'week',           name: 'Weeks' },
                { symbol: 'month',          name: 'Months'},
                { symbol: 'year',           name: 'Years' },
                { symbol: 'decade',         name: 'Decade' },
                { symbol: 'century',        name: 'Century' },
                { symbol: 'millennium',     name: 'Millennium' },
            ],
            defaults: ['minute', 'second']
        },
        mass: {
            name: "Mass",
            units: [
                { symbol: 'microgram',      name: 'Microgram' },
                { symbol: 'kilogram',       name: 'Kilogram' },
                { symbol: 'milligram',      name: 'Milligram' },
                { symbol: 'gram',           name: 'Gram' },
                { symbol: 'ton',            name: 'Ton' },
                { symbol: 'grain',          name: 'Grain' },
                { symbol: 'dram',           name: 'Dram' },
                { symbol: 'ounce',          name: 'Ounce' },
                { symbol: 'poundmass',      name: 'Pound' },
                { symbol: 'hundredweight',  name: 'Hundredweight' },
                { symbol: 'stick',          name: 'Stick' },
                { symbol: 'stone',          name: 'Stone' },
            ],
            defaults: ['kilogram', 'gram']
        },
        temperature: {
            name: "Temperature",
            units: [
                { symbol: 'kelvin',     name: 'Kelvin' },
                { symbol: 'celsius',    name: 'Celsius' },
                { symbol: 'fahrenheit', name: 'Fahrenheit' },
                { symbol: 'rankine',    name: 'Rankine' },
            ],
            defaults: ['celsius', 'fahrenheit']
        },
        force: {
            name: "Force",
            units: [
                { symbol: 'newton',     name: 'Newton' },
                { symbol: 'dyne',       name: 'Dyne'},
                { symbol: 'poundforce', name: 'Pound Force'},
                { symbol: 'kip',        name: 'Kip'},
            ],
            defaults: ['newton', 'dyne']
        },
        energy: {
            name: "Energy",
            units: [
                { symbol: 'joule',          name: 'Joule' },
                { symbol: 'kilojoule',      name: 'kilojoule' },
                { symbol: 'gramcalorie',    name: 'Gram Calorie'},
                { symbol: 'kilocalorie',    name: 'Kilo Calorie'},
                // 'killowatt/hr', ~~ add in support for this
                { symbol: 'Wh',             name: 'Wh' }, // add in the proper name for this
                { symbol: 'erg',            name: 'erg' },
                { symbol: 'BTU',            name: 'British Thermal Unit'},
                { symbol: 'electronvolt',   name: 'Electronvolt'},
            ],
            defaults: ['joule', 'Wh']
        },
        power: {
            name: "Power",
            units: [
                { symbol: 'watt', name: 'Watt'},
                { symbol: 'hp', name: 'HP' }
            ],
            defaults: ['watt', 'hp']
        },
        pressure: {
            name: "Pressure",
            units: [
                { symbol: 'Pa',     name: 'Pascal' },
                { symbol: 'psi',    name: 'Pounds per Square Inch' },
                { symbol: 'atm',    name: 'atm' }, // add in propert name
                { symbol: 'torr',   name: 'Torr' },
                { symbol: 'mmHg',   name: 'mmHg' }, // add in proper name
                { symbol: 'mmH2O',  name: 'mmH2O' }, // add in proper name
                { symbol: 'cmH20',  name: 'cmH20' }, // add in proper name
                { symbol: 'bar',    name: 'Bars' },
            ],
            defaults: ['Pa', 'psi']
        },
        digital: {
            name: "Digital Storage",
            units: [
                { symbol: 'b', name: 'Bit' },
                { symbol: 'B', name: 'Byte' },
                { symbol: 'kb', name: 'Kilobit' },
                { symbol: 'mb', name: 'Megabit'},
                { symbol: 'gb', name: 'Gigabit'},
                { symbol: 'Tb', name: 'Terrabit'},
                { symbol: 'KB', name: 'Kilobyte'},
                { symbol: 'MB', name: 'Megabyte'},
                { symbol: 'GB', name: 'Gigabyte'},
                { symbol: 'TB', name: 'Terabyte'},
                { symbol: 'PB', name: 'Petabyte'},
            ],
            defaults: ['b', 'B']
        },
    } // Units

    DDH.conversions.build = function(ops) {

        // just defaulting to `length` for now, will change when interacting with perl backend.
        var startBase = ops.data.physical_quantity || 'length';
        var leftUnit = ops.data.left_unit || Units[startBase].defaults[0];
        var rightUnit = ops.data.right_unit || Units[startBase].defaults[1];
        var rawInput = ops.data.raw_input || '1';
        var unitsSpecified = false;

        return {
            signal: "high",
            onShow: function() {
                DDG.require('math.js', function() {

                    if(!localDOMInitialized) {
                        Utils.setUpLocalDOM();
                        Utils.setUpCustomUnits();
                    }

                    if(!initialized) {
                        Converter.updateUnitSelectors(startBase);
                        Converter.updateBaseUnitSelector(startBase);

                        // if no numbers provided, fall back on 1
                        if(!unitsSpecified) {
                            $convert_left.val(rawInput);
                            $select_left.val(leftUnit);
                            $select_right.val(rightUnit);
                            Converter.convert("right");
                        }

                        initialized = true;
                    }

                    $convert_left.keyup(function( _e ) {
                        if(this.value === "") {
                            $convert_right.val("");
                        }
                        if(this.value !== "" && $.isNumeric(this.value)) {
                            Converter.convert("right");
                        }
                    });

                    $convert_right.keyup(function( _e ) {
                        if(this.value === "") {
                            $convert_left.val("");
                        }
                        if(this.value !== "" && $.isNumeric(this.value)) {
                            Converter.convert("left");
                        }
                    });

                    $convert_left.click(function() {
                        this.select()
                    });

                    $convert_right.click(function() {
                        this.select()
                    });

                    $select_right.change(function() {
                        Converter.convert("right");
                    });

                    $select_left.change(function() {
                        Converter.convert("left");
                    });

                    // if the user changes the unit base
                    $unitSelector.change(function() {
                        Converter.updateUnitSelectors(this.value);
                        $convert_left.val("1");
                        Converter.convert("right");
                    });

                });

            }// on show
        }; // return
    }; // DDH.conversions.build

    // checks we are not in the browser and exposes Converter for testing
    if (typeof window === 'undefined') {
        module.exports = { Converter: Converter };
    }

})(DDH);
<commit_msg>Add comments. General refactor.<commit_after>DDH.conversions = DDH.conversions || {};

(function(DDH) {
    "use strict";

    // flag variables for onShow functionality
    var localDOMInitialized = false;
    var initialized = false;

    // UI: the input / output fields
    var $convert_left,
        $convert_right,
        $select_right,
        $select_left,
        $unitSelector,
        $selects;

    // caches the local DOM vars
    function setUpLocalDOM() {

        var $root           = DDH.getDOM('conversions');
        $convert_left       = $root.find(".frm__input--left");
        $convert_right      = $root.find(".frm__input--right");
        $selects            = $root.find(".frm--top select");
        $select_right       = $root.find(".frm__select--right");
        $select_left        = $root.find(".frm__select--left");
        $unitSelector       = $root.find(".frm__select--bottom");
        localDOMInitialized = true;
    }

    // custom units that are not supported by math.js
    function setUpCustomUnits() {

        // CUSTOM ENERGY UNITS
        math.createUnit('kilojoule', '1000 joules');
        math.createUnit('gramcalorie', '4.184 joules');
        math.createUnit('kilocalorie', '4184 joules');

        // CUSTOM DIGITAL UNITS
        math.createUnit('kb', '1000 b');
        math.createUnit('mb', '1000000 b');
        math.createUnit('gb', '1000000000 b');
        math.createUnit('Tb', '1000000000000 b');
        math.createUnit('KB', '1000 B');
        math.createUnit('MB', '1000000 B');
        math.createUnit('GB', '1000000000 B');
        math.createUnit('TB', '1000 GB');
        math.createUnit('PB', '1000 TB');
    }

    /**
     * Converter
     *
     * The converter object is responsible for getting the values from
     * the UI and building the expression that is passed to the math object
     */
    var Converter = {

        // the local vars
        leftUnit:   "",
        rightUnit:  "",
        leftValue:  "",
        rightValue: "",

        setValues: function() {
            this.setLeftUnit();
            this.setRightUnit();
            this.setLeftValue();
            this.setRightValue();
        },

        setLeftUnit: function() {
            this.leftUnit = $select_left.val();
        },

        setLeftValue: function() {
            this.leftValue = $convert_left.val();
        },

        setRightUnit: function() {
            this.rightUnit = $select_right.val();
        },

        setRightValue: function() {
            this.rightValue = $convert_right.val();
        },

        eval: function( expression ) {
            return math.eval(expression).format({ precision: 6 }).split(" ")[0];
        },

        convert: function( side ) {

            this.setValues();
            if(side === "right") {
                var expression = this.leftValue + " " + this.leftUnit + " to " + this.rightUnit;
                $convert_right.val(this.eval(expression));
            } else {
                var expression = this.rightValue + " " + this.rightUnit + " to " + this.leftUnit;
                $convert_left.val(this.eval(expression));
            }
        },

        // removes all the options
        emptySelects: function() {

            $select_left.empty();
            $select_right.empty();
        },

        updateUnitSelectors: function( key ) {

            // resets the selects state
            this.emptySelects();
            // sort the keys alphabetically
            Units[key].units.sort();

            // adds the new conversion units to the selects
            for(var i = 0 ; i < Units[key].units.length ; i++) {
                $selects.append(
                    '<option value="' + Units[key].units[i].symbol + '">'
                    + Units[key].units[i].name
                    + '</option>'
                );
            }

            // set defaults. these should match Units[key].units[i].symbol
            $select_left.val(Units[key].defaults[0]);
            $select_right.val(Units[key].defaults[1]);
        },

        // updates the list of bases to choose from. Should only be called once (on startup)
        updateBaseUnitSelector: function( startBase ) {
            // adds the different unit types to the selector
            var unitKeys = Object.keys(Units);
            $.each(unitKeys.sort(), function(_key, value) {
                $unitSelector.append(
                    '<option value="'+value+'"' + (value === startBase ? " selected='selected'" : "") + '>'
                    + Units[value].name
                    + '</option>'
                );
            });
        }
    } // Converter

    /**
     * Units
     *
     * The bases and their units that we provide for the user
     */
    var Units = {
        angle: {
            name: "Angle",
            units: [
                { symbol: 'rad',        name: 'Radians' },
                { symbol: 'deg',        name: 'Degrees' },
                { symbol: 'grad',       name: 'Gradians' },
                { symbol: 'cycle',      name: 'Cycles' },
                { symbol: 'arcsec',     name: 'Arcsecond' },
                { symbol: 'arcmin',     name: 'Arcminute' },
                { symbol: 'millirad',   name: 'Milliradian' },
            ],
            defaults: ['deg', 'rad']
        },
        area: {
            name: "Area",
            units: [
                { symbol: 'm2',         name: 'Square Meter' },
                { symbol: 'sqin',       name: 'Square Inch' },
                { symbol: 'sqft',       name: 'Square Feet' },
                { symbol: 'sqyd',       name: 'Square Yard' },
                { symbol: 'sqmi',       name: 'Square Mile' },
                { symbol: 'acre',       name: 'Acre' },
                { symbol: 'hectare',    name: 'Hectare' }
            ],
            defaults: ['m2', 'sqin']
        },
        digital: {
            name: "Digital Storage",
            units: [
                { symbol: 'b', name: 'Bit' },
                { symbol: 'B', name: 'Byte' },
                { symbol: 'kb', name: 'Kilobit' },
                { symbol: 'mb', name: 'Megabit'},
                { symbol: 'gb', name: 'Gigabit'},
                { symbol: 'Tb', name: 'Terrabit'},
                { symbol: 'KB', name: 'Kilobyte'},
                { symbol: 'MB', name: 'Megabyte'},
                { symbol: 'GB', name: 'Gigabyte'},
                { symbol: 'TB', name: 'Terabyte'},
                { symbol: 'PB', name: 'Petabyte'},
            ],
            defaults: ['b', 'B']
        },
        duration: {
            name: "Duration",
            units: [
                { symbol: 'nanosecond',     name: 'Nanoseconds' },
                { symbol: 'microsecond',    name: 'Microseconds' },
                { symbol: 'millisecond',    name: 'Milliseconds' },
                { symbol: 'second',         name: 'Seconds' },
                { symbol: 'minute',         name: 'Minutes' },
                { symbol: 'hour',           name: 'Hours' },
                { symbol: 'day',            name: 'Days' },
                { symbol: 'week',           name: 'Weeks' },
                { symbol: 'month',          name: 'Months'},
                { symbol: 'year',           name: 'Years' },
                { symbol: 'decade',         name: 'Decade' },
                { symbol: 'century',        name: 'Century' },
                { symbol: 'millennium',     name: 'Millennium' },
            ],
            defaults: ['minute', 'second']
        },
        energy: {
            name: "Energy",
            units: [
                { symbol: 'joule',          name: 'Joule' },
                { symbol: 'kilojoule',      name: 'kilojoule' },
                { symbol: 'gramcalorie',    name: 'Gram Calorie'},
                { symbol: 'kilocalorie',    name: 'Kilo Calorie'},
                { symbol: 'Wh',             name: 'Watt Hour' },
                { symbol: 'erg',            name: 'erg' },
                { symbol: 'BTU',            name: 'British Thermal Unit'},
                { symbol: 'electronvolt',   name: 'Electronvolt'},
            ],
            defaults: ['joule', 'Wh']
        },
        force: {
            name: "Force",
            units: [
                { symbol: 'newton',     name: 'Newton' },
                { symbol: 'dyne',       name: 'Dyne'},
                { symbol: 'poundforce', name: 'Pound Force'},
                { symbol: 'kip',        name: 'Kip'},
            ],
            defaults: ['newton', 'dyne']
        },
        length: {
            name: "Length",
            units: [
                { symbol: 'decameter',  name: 'Decameter' },
                { symbol: 'millimeter', name: 'Millimeter' },
                { symbol: 'micrometer', name: 'Micrometer' },
                { symbol: 'micrometer', name: 'Micrometer' },
                { symbol: 'nanometer',  name: 'Nanometer' },
                { symbol: 'picometer',  name: 'Picometer' },
                { symbol: 'kilometer',  name: 'Kilometer' },
                { symbol: 'meter',      name: 'Meter' },
                { symbol: 'cm',         name: 'Centimeter' },
                { symbol: 'hectometer', name: 'Hectometer' },
                { symbol: 'chains',     name: 'Chains' },
                { symbol: 'inch',       name: 'Inch' },
                { symbol: 'foot',       name: 'Feet' },
                { symbol: 'yard',       name: 'Yard' },
                { symbol: 'mile',       name: 'Mile' },
                { symbol: 'link',       name: 'Link' },
                { symbol: 'rod',        name: 'Rod' },
                { symbol: 'angstrom',   name: 'Angstrom' },
                { symbol: 'mil',        name: 'Mil'}
            ],
            defaults: ['meter', 'cm']
        },
        liquid_volume: { // double check this works
            name: "Liquid Volume",
            units: [
                { symbol: 'minim',          name: 'Minim' },
                { symbol: 'fluiddram',      name: 'Fluid Dram' },
                { symbol: 'fluidounce',     name: 'Fluid Ounce' },
                { symbol: 'gill',           name: 'Gill' },
                { symbol: 'cup',            name: 'Cup' },
                { symbol: 'pint',           name: 'Pint'},
                { symbol: 'quart',          name: 'Quart'},
                { symbol: 'gallon',         name: 'Gallon'},
                { symbol: 'beerbarrel',     name: 'Beerbarrel'},
                { symbol: 'oilbarrel',      name: 'Oilbarrel'},
                { symbol: 'hogshead',       name: 'Hogshead'},
                { symbol: 'drop',           name: 'Drop'},
            ],
            defaults: ['minim', 'fluiddram']
        },
        mass: {
            name: "Mass",
            units: [
                { symbol: 'microgram',      name: 'Microgram' },
                { symbol: 'kilogram',       name: 'Kilogram' },
                { symbol: 'milligram',      name: 'Milligram' },
                { symbol: 'gram',           name: 'Gram' },
                { symbol: 'ton',            name: 'Ton' },
                { symbol: 'grain',          name: 'Grain' },
                { symbol: 'dram',           name: 'Dram' },
                { symbol: 'ounce',          name: 'Ounce' },
                { symbol: 'poundmass',      name: 'Pound' },
                { symbol: 'hundredweight',  name: 'Hundredweight' },
                { symbol: 'stick',          name: 'Stick' },
                { symbol: 'stone',          name: 'Stone' },
            ],
            defaults: ['kilogram', 'gram']
        },
        power: {
            name: "Power",
            units: [
                { symbol: 'watt', name: 'Watt'},
                { symbol: 'hp', name: 'HP' }
            ],
            defaults: ['watt', 'hp']
        },
        pressure: {
            name: "Pressure",
            units: [
                { symbol: 'Pa',     name: 'Pascal' },
                { symbol: 'psi',    name: 'PSI' },
                { symbol: 'atm',    name: 'Atmospheres' },
                { symbol: 'torr',   name: 'Torr' },
                { symbol: 'mmHg',   name: 'mmHg' },
                { symbol: 'mmH2O',  name: 'mmH2O' },
                { symbol: 'cmH20',  name: 'cmH20' },
                { symbol: 'bar',    name: 'Bars' },
            ],
            defaults: ['Pa', 'psi']
        },
        temperature: {
            name: "Temperature",
            units: [
                { symbol: 'kelvin',     name: 'Kelvin' },
                { symbol: 'celsius',    name: 'Celsius' },
                { symbol: 'fahrenheit', name: 'Fahrenheit' },
                { symbol: 'rankine',    name: 'Rankine' },
            ],
            defaults: ['celsius', 'fahrenheit']
        },
        volume: {
            name: "Volume",
            units: [
                { symbol: 'litre',      name: 'Litre' },
                { symbol: 'millilitre', name: 'Millilitre' },
                { symbol: 'hectolitre', name: 'Hectolitre' },
                { symbol: 'decalitre',  name: 'Decalitre' },
                { symbol: 'deciliter',  name: 'Deciliter' },
                { symbol: 'centilitre', name: 'Centilitre' },
                { symbol: 'cc',         name: 'CC' },
                { symbol: 'cuin',       name: 'Cuin' },
                { symbol: 'cuft',       name: 'Cuft' },
                { symbol: 'cups',       name: 'Cups' },
                { symbol: 'cuyd',       name: 'Cubic Yard' },
                { symbol: 'pints',      name: 'Pints' },
                { symbol: 'teaspoon',   name: 'Teaspoon' },
                { symbol: 'tablespoon', name: 'Tablespoon' },
            ],
            defaults: ['litre', 'millilitre']
        },
    } // Units

    DDH.conversions.build = function(ops) {

        // Defaults to length if no base is supported
        var startBase = ops.data.physical_quantity || 'length';
        var leftUnit = ops.data.left_unit || Units[startBase].defaults[0];
        var rightUnit = ops.data.right_unit || Units[startBase].defaults[1];
        var rawInput = ops.data.raw_input || '1';
        var unitsSpecified = false;

        return {
            // anytime this is triggered, we default to a high signal
            signal: "high",
            onShow: function() {
                DDG.require('math.js', function() {

                    // checks to see if custom units need set up and selectors cached
                    if(!localDOMInitialized) {
                        setUpLocalDOM();
                        setUpCustomUnits();
                    }

                    if(!initialized) {
                        Converter.updateUnitSelectors(startBase);
                        Converter.updateBaseUnitSelector(startBase);

                        // if no numbers provided, fall back on 1
                        if(!unitsSpecified) {
                            $convert_left.val(rawInput);
                            $select_left.val(leftUnit);
                            $select_right.val(rightUnit);
                            Converter.convert("right");
                        }

                        initialized = true;
                    }

                    $convert_left.keyup(function( _e ) {
                        if(this.value === "") {
                            $convert_right.val("");
                        }
                        if(this.value !== "" && $.isNumeric(this.value)) {
                            Converter.convert("right");
                        }
                    });

                    $convert_right.keyup(function( _e ) {
                        if(this.value === "") {
                            $convert_left.val("");
                        }
                        if(this.value !== "" && $.isNumeric(this.value)) {
                            Converter.convert("left");
                        }
                    });

                    $convert_left.click(function() {
                        this.select()
                    });

                    $convert_right.click(function() {
                        this.select()
                    });

                    $select_right.change(function() {
                        Converter.convert("right");
                    });

                    $select_left.change(function() {
                        Converter.convert("left");
                    });

                    // if the user changes the unit base
                    $unitSelector.change(function() {
                        Converter.updateUnitSelectors(this.value);
                        $convert_left.val("1");
                        Converter.convert("right");
                    });

                });

            }// on show
        }; // return
    }; // DDH.conversions.build

    // checks we are not in the browser and exposes Converter for unit testing
    if (typeof window === 'undefined') {
        module.exports = Converter;
    }

})(DDH);
<|endoftext|>


<commit_before>import _ from "underscore";
import React from "react";
const PropTypes = React.PropTypes;

import FormControl from "./FormControl";
import Util from "../utils/Util";

const METHODS_TO_BIND = [
  "handleSubmit",
  "handleBlur",
  "handleValueChange",
  "handleOnFocus"
];

function findFieldOption(options, field) {
  var flattenedOptions = _.flatten(options);
  return _.find(flattenedOptions, function (fieldOption) {
    return fieldOption.fieldName === field;
  });
}

export default class Form extends React.Component {
  constructor() {
    super();

    this.state = {
      prevSaveModel: {},
      model: {},
      editingField: "",
      erroredFields: {}
    };

    METHODS_TO_BIND.forEach((method) => {
      this[method] = this[method].bind(this);
    });
  }

  componentWillMount() {
    if (this.props.triggerSubmit) {
      this.props.triggerSubmit(this.handleSubmit.bind(this));
    }

    this.setState({
      model: this.buildModel(this.props.definition),
      prevSaveModel: this.buildModel(this.props.definition),
      erroredFields: this.buildErroredFields(this.props.definition)
    });
  }

  handleSubmit() {
    let validated = this.validateSubmit();

    if (validated && this.props.onSubmit) {
      this.props.onSubmit(this.state.model);
      this.setState({prevSaveModel: _.clone(this.state.model)});
      return;
    }
  }

  handleValueChange(field, value) {
    let model = _.clone(this.state.model);
    model[field] = value;

    this.setState({model});
  }

  handleBlur(fieldName) {
    let prevValue = this.state.prevSaveModel[fieldName];
    let currentValue = this.state.model[fieldName];

    if (prevValue !== currentValue) {
      this.handleSubmit();
    }

    this.setState({editingField: null});
  }

  handleOnFocus(fieldName) {
    let fieldOption = findFieldOption(this.props.definition, fieldName);

    if (fieldOption.writeType === "edit") {
      this.setState({editingField: fieldName});
    }
  }

  validateValue(field, value) {
    let validation = findFieldOption(this.props.definition, field).validation;

    if (typeof validation === "function") {
      return validation(value);
    }

    return validation.test(value);
  }

  validateSubmit() {
    var failedFields = _.filter(Object.keys(this.state.model), (fieldName) => {
      let model = this.state.model;
      let validated = this.validateValue(fieldName, model[fieldName]);

      if (!validated) {
        return true;
      }

      let options = findFieldOption(this.props.definition, fieldName);
      if (options.required) {
        return model[fieldName] == null;
      }

      return false;
    });

    let erroredFields = {};
    failedFields.forEach(function (field) {
      erroredFields[field] = true;
    });

    this.setState({erroredFields});

    return failedFields.length === 0;
  }

  buildModel(definition) {
    let model = {};

    definition.forEach((formControlOption) => {
      if (Util.isArray(formControlOption)) {
        _.extend(model, this.buildModel(formControlOption));
        return;
      }

      model[formControlOption.fieldName] = formControlOption.value || null;
    });

    return model;
  }

  buildErroredFields(definition) {
    let flattenedOptions = _.flatten(definition);
    let erroredFields = {};

    flattenedOptions.forEach((formControlOption) => {
      if (formControlOption.showError) {
        erroredFields[formControlOption.fieldName] = true;
      }
    });

    return erroredFields;
  }

  getFormControls(definition) {
    let flattenedOptions = _.flatten(definition);

    return flattenedOptions.map((formControlOption, i) => {
      let fieldName = formControlOption.fieldName;
      let currentlyEditing = fieldName === this.state.editingField;

      let showError = this.state.erroredFields[fieldName];

      return (
        <FormControl
          key={i}
          definition={formControlOption}
          onBlur={this.handleBlur}
          onFocus={this.handleOnFocus}
          triggerSubmit={this.handleSubmit}
          onChange={this.handleValueChange}
          editing={currentlyEditing}
          validationError={showError}
          currentValue={this.state.model[fieldName]} />
      );
    });
  }

  render() {
    let formControls = this.getFormControls(this.props.definition);

    return (
      <form>
        {formControls}
      </form>
    );
  }
}

Form.propTypes = {
  definition: PropTypes.array,
  triggerSubmit: PropTypes.func,
  onSubmit: PropTypes.func

};

Form.defaultProps = {
  triggerSubmit: function () {},
  onSubmit: function () {},
  definition: {}
};

/*
  TODO:
  -x- Util to combine schema/options
  -x- Different field types
  -x- Required fields
  -x- Placeholders
  -x- Only validate on submit
  -x- Submission within form
  -x- Validation error shows help text
  -x- Handle form submit (it takes a callback and passes in the model)
  -x- Have the current values be the value of the input
  Switch modes between edit, read, input (also, elements)
  (another PR) Figure out what a schema looks like and how to better merge it
*/
<commit_msg>Remove prevSaveModel. maybe in the future<commit_after>import _ from "underscore";
import React from "react";
const PropTypes = React.PropTypes;

import FormControl from "./FormControl";
import Util from "../utils/Util";

const METHODS_TO_BIND = [
  "handleSubmit",
  "handleBlur",
  "handleValueChange",
  "handleOnFocus"
];

function findFieldOption(options, field) {
  var flattenedOptions = _.flatten(options);
  return _.find(flattenedOptions, function (fieldOption) {
    return fieldOption.fieldName === field;
  });
}

export default class Form extends React.Component {
  constructor() {
    super();

    this.state = {
      model: {},
      editingField: "",
      erroredFields: {}
    };

    METHODS_TO_BIND.forEach((method) => {
      this[method] = this[method].bind(this);
    });
  }

  componentWillMount() {
    if (this.props.triggerSubmit) {
      this.props.triggerSubmit(this.handleSubmit.bind(this));
    }

    this.setState({
      model: this.buildModel(this.props.definition),
      erroredFields: this.buildErroredFields(this.props.definition)
    });
  }

  handleSubmit() {
    let validated = this.validateSubmit();

    if (validated && this.props.onSubmit) {
      this.props.onSubmit(this.state.model);
      return;
    }
  }

  handleValueChange(field, value) {
    let model = _.clone(this.state.model);
    model[field] = value;

    this.setState({model});
  }

  handleBlur() {
    this.handleSubmit();

    this.setState({editingField: null});
  }

  handleOnFocus(fieldName) {
    let fieldOption = findFieldOption(this.props.definition, fieldName);

    if (fieldOption.writeType === "edit") {
      this.setState({editingField: fieldName});
    }
  }

  validateValue(field, value) {
    let validation = findFieldOption(this.props.definition, field).validation;

    if (typeof validation === "function") {
      return validation(value);
    }

    return validation.test(value);
  }

  validateSubmit() {
    var failedFields = _.filter(Object.keys(this.state.model), (fieldName) => {
      let model = this.state.model;
      let validated = this.validateValue(fieldName, model[fieldName]);

      if (!validated) {
        return true;
      }

      let options = findFieldOption(this.props.definition, fieldName);
      if (options.required) {
        return model[fieldName] == null;
      }

      return false;
    });

    let erroredFields = {};
    failedFields.forEach(function (field) {
      erroredFields[field] = true;
    });

    this.setState({erroredFields});

    return failedFields.length === 0;
  }

  buildModel(definition) {
    let model = {};

    definition.forEach((formControlOption) => {
      if (Util.isArray(formControlOption)) {
        _.extend(model, this.buildModel(formControlOption));
        return;
      }

      model[formControlOption.fieldName] = formControlOption.value || null;
    });

    return model;
  }

  buildErroredFields(definition) {
    let flattenedOptions = _.flatten(definition);
    let erroredFields = {};

    flattenedOptions.forEach((formControlOption) => {
      if (formControlOption.showError) {
        erroredFields[formControlOption.fieldName] = true;
      }
    });

    return erroredFields;
  }

  getFormControls(definition) {
    let flattenedOptions = _.flatten(definition);

    return flattenedOptions.map((formControlOption, i) => {
      let fieldName = formControlOption.fieldName;
      let currentlyEditing = fieldName === this.state.editingField;

      let showError = this.state.erroredFields[fieldName];

      return (
        <FormControl
          key={i}
          definition={formControlOption}
          onBlur={this.handleBlur}
          onFocus={this.handleOnFocus}
          triggerSubmit={this.handleSubmit}
          onChange={this.handleValueChange}
          editing={currentlyEditing}
          validationError={showError}
          currentValue={this.state.model[fieldName]} />
      );
    });
  }

  render() {
    let formControls = this.getFormControls(this.props.definition);

    return (
      <form>
        {formControls}
      </form>
    );
  }
}

Form.propTypes = {
  definition: PropTypes.array,
  triggerSubmit: PropTypes.func,
  onSubmit: PropTypes.func

};

Form.defaultProps = {
  triggerSubmit: function () {},
  onSubmit: function () {},
  definition: {}
};
<|endoftext|>


<commit_before>import _ from "underscore";
import React from "react";
const PropTypes = React.PropTypes;

import FormControl from "./FormControl";
import Util from "../utils/Util";

const METHODS_TO_BIND = [
  "handleBlur",
  "handleEvent",
  "handleOnFocus",
  "handleSubmit",
  "handleValueChange"
];

// Find a the options for a particular field in the form.
function findFieldOption(options, field) {
  let flattenedOptions = _.flatten(options);
  return _.find(flattenedOptions, function (fieldOption) {
    return fieldOption.name === field;
  });
}

export default class Form extends React.Component {
  constructor() {
    super();

    this.state = {
      model: {},
      editingField: "",
      erroredFields: {}
    };

    METHODS_TO_BIND.forEach((method) => {
      this[method] = this[method].bind(this);
    });
  }

  componentWillMount() {
    if (this.props.triggerSubmit) {
      this.props.triggerSubmit(this.handleSubmit);
    }

    this.setState({
      model: this.buildStateObj(this.props.definition, "value"),
      erroredFields: this.buildStateObj(this.props.definition, "showError")
    });
  }

  componentWillReceiveProps(nextProps) {
    let nextState = {};
    let currentModel = this.buildStateObj(this.props.definition, "value");
    let nextModel = this.buildStateObj(nextProps.definition, "value");
    if (!_.isEqual(currentModel, nextModel)) {
      nextState.model = _.extend({}, this.state.model, nextModel);
    }

    let currentErrors = this.buildStateObj(this.props.definition, "showError");
    let nextErrors = this.buildStateObj(nextProps.definition, "showError");
    if (!_.isEqual(currentErrors, nextErrors)) {
      nextState.erroredFields = _.extend(
        {}, this.state.erroredFields, nextErrors
      );
    }

    this.setState(nextState);
  }

  handleEvent(eventType, fieldName, fieldValue) {
    switch (eventType) {
      case "blur":
        this.handleBlur(fieldName);
        break;
      case "change":
        this.handleValueChange(fieldName, fieldValue);
        break;
      case "focus":
        this.handleOnFocus(fieldName);
        break;
    }

    this.props.onChange(this.state.model);
  }

  handleSubmit(event) {
    if (event) {
      event.preventDefault();
    }

    let model = this.state.model;
    let props = this.props;
    let validated = this.validateSubmit(
      model, props.definition
    );

    if (validated && props.onSubmit) {
      props.onSubmit(model);
      return;
    }
  }

  handleValueChange(field, value) {
    let model = _.clone(this.state.model);
    model[field] = value;

    this.setState({model});
  }

  handleBlur(field) {
    let options = findFieldOption(this.props.definition, field);

    if (options.writeType === "input") {
      return;
    }

    this.handleSubmit();
    this.setState({editingField: null});
  }

  handleOnFocus(name) {
    let fieldOption = findFieldOption(this.props.definition, name);

    if (fieldOption.writeType === "edit") {
      this.setState({editingField: name});
    }
  }

  validateValue(field, value, definition) {
    let validation = findFieldOption(definition, field).validation;

    if (validation == null) {
      return true;
    }

    if (typeof validation === "function") {
      return validation(value);
    }

    return validation.test(value);
  }

  validateSubmit(model, definition) {
    let erroredFields = {};
    let submitValidated = true;

    Object.keys(model).forEach((name) => {
      let fieldValue = model[name];
      let options = findFieldOption(definition, name);
      let validated = this.validateValue(
        name, fieldValue, definition
      );

      if (!validated) {
        erroredFields[name] = options.validationErrorText;
        submitValidated = false;
        return;
      }

      if (options.required && (fieldValue == null || fieldValue === "")) {
        erroredFields[name] = "Field cannot be empty.";
        submitValidated = false;
      }
    });

    // Set the errored fields into state so we can render correctly.
    this.setState({erroredFields});

    return submitValidated;
  }

  buildStateObj(definition, fieldProp) {
    let stateObj = {};

    _.flatten(definition).forEach((formControlOption) => {
      stateObj[formControlOption.name] = formControlOption[fieldProp] || null;
    });

    return stateObj;
  }

  buildFormPropObj(formControlOption, stateObj) {
    let name = formControlOption.name;
    let isArray = Util.isArray(formControlOption);
    let propObj = {};

    if (isArray) {
      formControlOption.forEach((option) => {
        propObj[option.name] =
          stateObj[option.name];
      });
    } else {
      propObj[name] = stateObj[name];
    }

    return propObj;
  }

  getFormControls(definition) {
    let state = this.state;
    let classes = _.pick(
      this.props,
      "readClass",
      "inputClass",
      "formControlClass",
      "helpBlockClass"
    );

    return definition.map((formControlOption, i) => {
      // Map each field to showError boolean
      let showError =
        this.buildFormPropObj(formControlOption, state.erroredFields);

      // Map each field to it's current value.
      let currentValue =
        this.buildFormPropObj(formControlOption, state.model);

      return (
        <FormControl
          key={i}
          definition={formControlOption}
          editing={state.editingField}
          validationError={showError}
          currentValue={currentValue}
          handleEvent={this.handleEvent}
          maxColumnWidth={this.props.maxColumnWidth}
          {...classes} />
      );
    });
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit} className={this.props.className}>
        {this.getFormControls(this.props.definition)}
      </form>
    );
  }
}

Form.propTypes = {
  // Classes.
  className: PropTypes.string,
  formControlClass: PropTypes.string,
  helpBlockClass: PropTypes.string,
  inputClass: PropTypes.string,
  readClass: PropTypes.string,
  sharedClass: PropTypes.string,

  definition: PropTypes.array,
  maxColumnWidth: PropTypes.number,
  onChange: PropTypes.func,
  onSubmit: PropTypes.func,
  triggerSubmit: PropTypes.func
};

Form.defaultProps = {
  // Classes.
  className: "form flush-bottom",
  formControlClass: "row form-group",
  helpBlockClass: "form-help-block",
  inputClass: "form-control",
  readClass: "read-only",

  definition: {},
  onChange: function () {},
  onSubmit: function () {},
  maxColumnWidth: 12,
  triggerSubmit: function () {}
};
<commit_msg>Add inlineIconClass and inlineTextClass, pass along to fields<commit_after>import _ from "underscore";
import React from "react";
const PropTypes = React.PropTypes;

import FormControl from "./FormControl";
import Util from "../utils/Util";

const METHODS_TO_BIND = [
  "handleBlur",
  "handleEvent",
  "handleOnFocus",
  "handleSubmit",
  "handleValueChange"
];

// Find a the options for a particular field in the form.
function findFieldOption(options, field) {
  let flattenedOptions = _.flatten(options);
  return _.find(flattenedOptions, function (fieldOption) {
    return fieldOption.name === field;
  });
}

export default class Form extends React.Component {
  constructor() {
    super();

    this.state = {
      model: {},
      editingField: "",
      erroredFields: {}
    };

    METHODS_TO_BIND.forEach((method) => {
      this[method] = this[method].bind(this);
    });
  }

  componentWillMount() {
    if (this.props.triggerSubmit) {
      this.props.triggerSubmit(this.handleSubmit);
    }

    this.setState({
      model: this.buildStateObj(this.props.definition, "value"),
      erroredFields: this.buildStateObj(this.props.definition, "showError")
    });
  }

  componentWillReceiveProps(nextProps) {
    let nextState = {};
    let currentModel = this.buildStateObj(this.props.definition, "value");
    let nextModel = this.buildStateObj(nextProps.definition, "value");
    if (!_.isEqual(currentModel, nextModel)) {
      nextState.model = _.extend({}, this.state.model, nextModel);
    }

    let currentErrors = this.buildStateObj(this.props.definition, "showError");
    let nextErrors = this.buildStateObj(nextProps.definition, "showError");
    if (!_.isEqual(currentErrors, nextErrors)) {
      nextState.erroredFields = _.extend(
        {}, this.state.erroredFields, nextErrors
      );
    }

    this.setState(nextState);
  }

  handleEvent(eventType, fieldName, fieldValue) {
    switch (eventType) {
      case "blur":
        this.handleBlur(fieldName);
        break;
      case "change":
        this.handleValueChange(fieldName, fieldValue);
        break;
      case "focus":
        this.handleOnFocus(fieldName);
        break;
    }

    this.props.onChange(this.state.model);
  }

  handleSubmit(event) {
    if (event) {
      event.preventDefault();
    }

    let model = this.state.model;
    let props = this.props;
    let validated = this.validateSubmit(
      model, props.definition
    );

    if (validated && props.onSubmit) {
      props.onSubmit(model);
      return;
    }
  }

  handleValueChange(field, value) {
    let model = _.clone(this.state.model);
    model[field] = value;

    this.setState({model});
  }

  handleBlur(field) {
    let options = findFieldOption(this.props.definition, field);

    if (options.writeType === "input") {
      return;
    }

    this.handleSubmit();
    this.setState({editingField: null});
  }

  handleOnFocus(name) {
    let fieldOption = findFieldOption(this.props.definition, name);

    if (fieldOption.writeType === "edit") {
      this.setState({editingField: name});
    }
  }

  validateValue(field, value, definition) {
    let validation = findFieldOption(definition, field).validation;

    if (validation == null) {
      return true;
    }

    if (typeof validation === "function") {
      return validation(value);
    }

    return validation.test(value);
  }

  validateSubmit(model, definition) {
    let erroredFields = {};
    let submitValidated = true;

    Object.keys(model).forEach((name) => {
      let fieldValue = model[name];
      let options = findFieldOption(definition, name);
      let validated = this.validateValue(
        name, fieldValue, definition
      );

      if (!validated) {
        erroredFields[name] = options.validationErrorText;
        submitValidated = false;
        return;
      }

      if (options.required && (fieldValue == null || fieldValue === "")) {
        erroredFields[name] = "Field cannot be empty.";
        submitValidated = false;
      }
    });

    // Set the errored fields into state so we can render correctly.
    this.setState({erroredFields});

    return submitValidated;
  }

  buildStateObj(definition, fieldProp) {
    let stateObj = {};

    _.flatten(definition).forEach((formControlOption) => {
      stateObj[formControlOption.name] = formControlOption[fieldProp] || null;
    });

    return stateObj;
  }

  buildFormPropObj(formControlOption, stateObj) {
    let name = formControlOption.name;
    let isArray = Util.isArray(formControlOption);
    let propObj = {};

    if (isArray) {
      formControlOption.forEach((option) => {
        propObj[option.name] =
          stateObj[option.name];
      });
    } else {
      propObj[name] = stateObj[name];
    }

    return propObj;
  }

  getFormControls(definition) {
    let state = this.state;
    let classes = _.pick(
      this.props,
      "readClass",
      "inputClass",
      "formControlClass",
      "helpBlockClass",
      "sharedClass",
      "inlineIconClass",
      "inlineTextClass"
    );

    return definition.map((formControlOption, i) => {
      // Map each field to showError boolean
      let showError =
        this.buildFormPropObj(formControlOption, state.erroredFields);

      // Map each field to it's current value.
      let currentValue =
        this.buildFormPropObj(formControlOption, state.model);

      return (
        <FormControl
          key={i}
          definition={formControlOption}
          editing={state.editingField}
          validationError={showError}
          currentValue={currentValue}
          handleEvent={this.handleEvent}
          maxColumnWidth={this.props.maxColumnWidth}
          {...classes} />
      );
    });
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit} className={this.props.className}>
        {this.getFormControls(this.props.definition)}
      </form>
    );
  }
}

Form.propTypes = {
  // Classes.
  className: PropTypes.string,
  formControlClass: PropTypes.string,
  helpBlockClass: PropTypes.string,
  inlineIconClass: PropTypes.string,
  inlineTextClass: PropTypes.string,
  inputClass: PropTypes.string,
  readClass: PropTypes.string,
  sharedClass: PropTypes.string,

  definition: PropTypes.array,
  maxColumnWidth: PropTypes.number,
  onChange: PropTypes.func,
  onSubmit: PropTypes.func,
  triggerSubmit: PropTypes.func
};

Form.defaultProps = {
  // Classes.
  className: "form flush-bottom",
  formControlClass: "row form-group",
  helpBlockClass: "form-help-block",
  inlineIconClass: "form-element-inline-icon",
  inlineTextClass: "form-element-inline-text",
  inputClass: "form-control",
  readClass: "read-only",
  sharedClass: "form-element-inline",

  definition: {},
  onChange: function () {},
  onSubmit: function () {},
  maxColumnWidth: 12,
  triggerSubmit: function () {}
};
<|endoftext|>


<commit_before>// ==UserScript==
// @name         Duolingo - Easy Accents
// @description  Makes typing characters with accents and diacritics easy! Just use the Alt key!
// @match        *://www.duolingo.com/*
// @author       @HodofHod
// @version      0.1.6
// ==/UserScript==

// Technically, these are accents, diacritics, and ligatures,
// but accents is the commonest of those terms so that's the script's title.
// Some languages have characters that can be accented in many different ways
// (like the French 'e' or the Portuguese 'a'). While those letters are reachable by tapping ALT multiple times,
// it may become unweildy and annoying. I am open to other suggestions.

function inject(f) {
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.setAttribute('name', 'easy_accents');
    script.textContent = '(' + f.toString() + ')()';
    document.body.appendChild(script);
}

inject(main);
function main(){
    console.log('Duo Easy Accents');
    var maps = {
        es: {'A':'á', 'E':'é', 'I':'í', 'O':'ó', 'U':'úü', 'N':'ñ', '1':'¡', '!':'¡', '?':'¿'},
        fr: {'A':'àâæ', 'E':'èéêë', 'I':'îï', 'O':'ôœ', 'U':'ùûü', 'C':'ç'},
        pt: {'A':'ãáâà', 'E':'éê', 'I':'í', 'O':'õóô', 'U':'úü', 'C':'ç'},
        de: {'A':'ä', 'O':'ö', 'U':'ü', 'S':'ß'},
        it: {'A':'àá', 'E':'èé', 'I':'ìí', 'O':'òó', 'U':'ùú'},
        pl: {'A':'ą', 'C':'ć', 'E':'ę', 'L':'ł', 'N':'ń', 'O':'ó', 'S':'ś', 'Z':'źż'},
        ro: {'A':'ăâ', 'I':'î', 'S':'şș', 'T':'ţț'},
        hu: {'A':'á', 'E':'é', 'I':'í', 'O':'öóő', 'U':'üúű'},
        dn: {'E':'éë', 'I':'ï', 'O':'óö', 'U':'ü'},
        tr: {'C':'ç', 'G':'ğ', 'I':'ıİ', 'O':'ö', 'S':'ş', 'U':'ü'}
        },
        taps = 0,
        last_press = [];

    $(document).on('keydown', '[lang][lang!=en]', function (e) {
        //If the pressed key isn't already pressed, return
        if (!e.altKey || e.which===18) { return; }
        //If the same key is pressed in a short period of time, increment taps
        taps = (last_press[0] === e.which && new Date() - last_press[1] <= 750) ? taps+1 : 1;
        //Get accented chr; taps-1 is the 0-indexed accented chr to get.
        var chr = get_char(e.target.lang, String.fromCharCode(e.which), taps-1);
        if (!chr){ return false; }
        chr = e.shiftKey ? chr.toUpperCase() : chr;
        //Insert chr into textarea; Replace last chr for multiple taps by taps>1
        insert_char(this, chr, taps>1);
        last_press = [e.which, new Date()];
        //To override any other Alt+<x> hotkeys.
        return false;
    });

    function get_char(lang, base, index){
        if (!maps[lang]){ return false; }
        var char_lst = maps[lang][base];
        return char_lst ? char_lst[index % char_lst.length] : false;
    }

    function insert_char(textarea, new_char, del){
        var text = textarea.value,
            start = textarea.selectionStart,
            end = textarea.selectionEnd;
        //Insert the character. If we're rotating through alternate letters, remove the last character.
        textarea.value = text.slice(0, del ? end-1 : start) + new_char + text.slice(end);
        //Move the caret. If deleting the previous, the caret should remain. (Hence x+!del)
        //If replacing selected text (and not deleting previous), caret should unselect and be start+1
        textarea.setSelectionRange(start+!del, (end-start&&!del ? start : end) +!del);
    }
    $(document).on('keyup', '[lang][lang!=en]', function (e){
        if (e.which === 18){ //ALT keyup
            //Reset the last tapped key.
            last_press = [];
            //This ALT keyup isn't from an ALT+<x> combo, so modify the last char
            if (taps===0){
                var base = this.value.slice(this.selectionEnd-1, this.selectionEnd),
                    index;
                $.each(maps[e.target.lang], function(key, val){
                    index = val.indexOf(base.toLowerCase());
                    if (index > -1){
                        base = (base.toUpperCase() === base) ? key : key.toLowerCase();
                        return false;
                    }
                });
                var chr = get_char(e.target.lang, base.toUpperCase(), index+1);
                if (!chr){ return false; }
                chr = /[A-Z]/.test(base) ? chr.toUpperCase() : chr;
                insert_char(this, chr, true);
            }
            taps = 0;
            return false;
        }
    });
}
<commit_msg>Add Copyright and link to MIT License<commit_after>// ==UserScript==
// @name         Duolingo - Easy Accents
// @description  Makes typing characters with accents and diacritics easy! Just use the Alt key!
// @match        *://www.duolingo.com/*
// @author       @HodofHod
// @version      0.1.6
// ==/UserScript==

/*
Copyright (c) 2013-2014 HodofHod (https://github.com/HodofHod)

Licensed under the MIT License (MIT)
Full text of the license is available at https://raw2.github.com/HodofHod/Userscripts/master/LICENSE
*/

// Technically, these are accents, diacritics, and ligatures,
// but accents is the commonest of those terms so that's the script's title.
// Some languages have characters that can be accented in many different ways
// (like the French 'e' or the Portuguese 'a'). While those letters are reachable by tapping ALT multiple times,
// it may become unweildy and annoying. I am open to other suggestions.

function inject(f) {
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.setAttribute('name', 'easy_accents');
    script.textContent = '(' + f.toString() + ')()';
    document.body.appendChild(script);
}

inject(main);
function main(){
    console.log('Duo Easy Accents');
    var maps = {
        es: {'A':'á', 'E':'é', 'I':'í', 'O':'ó', 'U':'úü', 'N':'ñ', '1':'¡', '!':'¡', '?':'¿'},
        fr: {'A':'àâæ', 'E':'èéêë', 'I':'îï', 'O':'ôœ', 'U':'ùûü', 'C':'ç'},
        pt: {'A':'ãáâà', 'E':'éê', 'I':'í', 'O':'õóô', 'U':'úü', 'C':'ç'},
        de: {'A':'ä', 'O':'ö', 'U':'ü', 'S':'ß'},
        it: {'A':'àá', 'E':'èé', 'I':'ìí', 'O':'òó', 'U':'ùú'},
        pl: {'A':'ą', 'C':'ć', 'E':'ę', 'L':'ł', 'N':'ń', 'O':'ó', 'S':'ś', 'Z':'źż'},
        ro: {'A':'ăâ', 'I':'î', 'S':'şș', 'T':'ţț'},
        hu: {'A':'á', 'E':'é', 'I':'í', 'O':'öóő', 'U':'üúű'},
        dn: {'E':'éë', 'I':'ï', 'O':'óö', 'U':'ü'},
        tr: {'C':'ç', 'G':'ğ', 'I':'ıİ', 'O':'ö', 'S':'ş', 'U':'ü'}
        },
        taps = 0,
        last_press = [];

    $(document).on('keydown', '[lang][lang!=en]', function (e) {
        //If the pressed key isn't already pressed, return
        if (!e.altKey || e.which===18) { return; }
        //If the same key is pressed in a short period of time, increment taps
        taps = (last_press[0] === e.which && new Date() - last_press[1] <= 750) ? taps+1 : 1;
        //Get accented chr; taps-1 is the 0-indexed accented chr to get.
        var chr = get_char(e.target.lang, String.fromCharCode(e.which), taps-1);
        if (!chr){ return false; }
        chr = e.shiftKey ? chr.toUpperCase() : chr;
        //Insert chr into textarea; Replace last chr for multiple taps by taps>1
        insert_char(this, chr, taps>1);
        last_press = [e.which, new Date()];
        //To override any other Alt+<x> hotkeys.
        return false;
    });

    function get_char(lang, base, index){
        if (!maps[lang]){ return false; }
        var char_lst = maps[lang][base];
        return char_lst ? char_lst[index % char_lst.length] : false;
    }

    function insert_char(textarea, new_char, del){
        var text = textarea.value,
            start = textarea.selectionStart,
            end = textarea.selectionEnd;
        //Insert the character. If we're rotating through alternate letters, remove the last character.
        textarea.value = text.slice(0, del ? end-1 : start) + new_char + text.slice(end);
        //Move the caret. If deleting the previous, the caret should remain. (Hence x+!del)
        //If replacing selected text (and not deleting previous), caret should unselect and be start+1
        textarea.setSelectionRange(start+!del, (end-start&&!del ? start : end) +!del);
    }
    $(document).on('keyup', '[lang][lang!=en]', function (e){
        if (e.which === 18){ //ALT keyup
            //Reset the last tapped key.
            last_press = [];
            //This ALT keyup isn't from an ALT+<x> combo, so modify the last char
            if (taps===0){
                var base = this.value.slice(this.selectionEnd-1, this.selectionEnd),
                    index;
                $.each(maps[e.target.lang], function(key, val){
                    index = val.indexOf(base.toLowerCase());
                    if (index > -1){
                        base = (base.toUpperCase() === base) ? key : key.toLowerCase();
                        return false;
                    }
                });
                var chr = get_char(e.target.lang, base.toUpperCase(), index+1);
                if (!chr){ return false; }
                chr = /[A-Z]/.test(base) ? chr.toUpperCase() : chr;
                insert_char(this, chr, true);
            }
            taps = 0;
            return false;
        }
    });
}
<|endoftext|>


<commit_before>/**
 * @file Grid component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import isArray from 'lodash/isArray';
import classNames from 'classnames';

import GridItem from '../_GridItem';
import Tip from '../Tip';
import Theme from '../Theme';

import SelectMode from '../_statics/SelectMode';

import Util from '../_vendors/Util';
import Event from '../_vendors/Event';
import Calculation from '../_vendors/Calculation';

class Grid extends Component {

    static SelectMode = SelectMode;
    static Theme = Theme;

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.state = {
            value: Calculation.getInitValue(props)
        };

    }

    listItemSelectHandler = (item, index) => {

        const {selectMode} = this.props;

        let {value} = this.state;

        if (selectMode === SelectMode.MULTI_SELECT) {

            if (!value || !isArray(value)) {
                value = [];
            }

            value.push(item);

        } else if (selectMode === SelectMode.SINGLE_SELECT) {
            value = item;
        }

        this.setState({
            value
        }, () => {
            const {onItemSelect, onChange} = this.props;
            onItemSelect && onItemSelect(item, index);
            onChange && onChange(value, index);
        });

    };

    listItemDeselectHandler = (item, index) => {

        const {selectMode} = this.props;

        if (selectMode !== SelectMode.MULTI_SELECT) {
            return;
        }

        const {valueField, displayField} = this.props;
        let {value} = this.state;

        if (!value || !isArray(value)) {
            value = [];
        } else {
            value = value.filter(valueItem => {
                return Util.getValueByValueField(valueItem, valueField, displayField)
                    != Util.getValueByValueField(item, valueField, displayField);
            });
        }

        this.setState({
            value
        }, () => {
            const {onItemDeselect, onChange} = this.props;
            onItemDeselect && onItemDeselect(item, index);
            onChange && onChange(value, index);
        });

    };

    componentWillReceiveProps(nextProps) {
        if (nextProps.value !== this.state.value) {
            this.setState({
                value: Calculation.getInitValue(nextProps)
            });
        }
    }

    renderGridItem = (item, index) => {

        if (!item) {
            return;
        }

        const {

                theme, itemHeight, col,

                selectTheme, selectMode, radioUncheckedIconCls, radioCheckedIconCls,
                checkboxUncheckedIconCls, checkboxCheckedIconCls, checkboxIndeterminateIconCls,

                idField, valueField, displayField, descriptionField, disabled, isLoading, renderer, onItemClick,
                parentEl

            } = this.props,
            {value} = this.state;

        return typeof item === 'object' ?
            (
                <GridItem key={(idField in item && item[idField]) || index}
                          {...item}
                          index={index}
                          style={{height: itemHeight}}
                          theme={item.theme || theme}
                          parentEl={parentEl}
                          col={col}
                          selectTheme={item.selectTheme || selectTheme}
                          radioUncheckedIconCls={item.radioUncheckedIconCls || radioUncheckedIconCls}
                          radioCheckedIconCls={item.radioCheckedIconCls || radioCheckedIconCls}
                          checkboxUncheckedIconCls={item.checkboxUncheckedIconCls || checkboxUncheckedIconCls}
                          checkboxCheckedIconCls={item.checkboxCheckedIconCls || checkboxCheckedIconCls}
                          checkboxIndeterminateIconCls={item.checkboxIndeterminateIconCls || checkboxIndeterminateIconCls}
                          data={item}
                          checked={Calculation.isItemChecked(item, value, this.props)}
                          value={Util.getValueByValueField(item, valueField, displayField)}
                          text={Util.getTextByDisplayField(item, displayField, valueField)}
                          desc={item[descriptionField] || null}
                          disabled={disabled || item.disabled}
                          isLoading={isLoading || item.isLoading}
                          selectMode={selectMode}
                          renderer={renderer}
                          onClick={e => {
                              onItemClick && onItemClick(item, index, e);
                              item.onClick && item.onClick(e);
                          }}
                          onSelect={() => {
                              this.listItemSelectHandler(item, index);
                          }}
                          onDeselect={() => {
                              this.listItemDeselectHandler(item, index);
                          }}/>
            )
            :
            (
                <GridItem key={index}
                          index={index}
                          style={{height: itemHeight}}
                          theme={item.theme || theme}
                          parentEl={parentEl}
                          col={col}
                          selectTheme={item.selectTheme || selectTheme}
                          radioUncheckedIconCls={item.radioUncheckedIconCls || radioUncheckedIconCls}
                          radioCheckedIconCls={item.radioCheckedIconCls || radioCheckedIconCls}
                          checkboxUncheckedIconCls={item.checkboxUncheckedIconCls || checkboxUncheckedIconCls}
                          checkboxCheckedIconCls={item.checkboxCheckedIconCls || checkboxCheckedIconCls}
                          checkboxIndeterminateIconCls={item.checkboxIndeterminateIconCls || checkboxIndeterminateIconCls}
                          data={item}
                          checked={Calculation.isItemChecked(item, value, this.props)}
                          value={item}
                          text={item}
                          disabled={disabled}
                          isLoading={isLoading}
                          selectMode={selectMode}
                          renderer={renderer}
                          onClick={e => {
                              onItemClick && onItemClick(item, index, e);
                          }}
                          onSelect={() => {
                              this.listItemSelectHandler(item, index);
                          }}
                          onDeselect={() => {
                              this.listItemDeselectHandler(item, index);
                          }}/>
            );

    };

    render() {

        const {children, className, style, data, disabled} = this.props,

            gridClassName = classNames('grid', {
                [className]: className
            });

        return (

 Event.wheelHandler(e, this.props)}>

                {data && data.map((item, index) => this.renderGridItem(item, index))}

                {children}

        );
    }
}

Grid.propTypes = {

    /**
     * The CSS class name of the root element.
     */
    className: PropTypes.string,

    /**
     * Override the styles of the root element.
     */
    style: PropTypes.object,

    /**
     * The theme of the grid item.
     */
    theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    /**
     * The theme of the grid item select radio or checkbox.
     */
    selectTheme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    /**
     * The mode of listItem.
     */
    selectMode: PropTypes.oneOf(Util.enumerateValue(SelectMode)),

    /**
     * Children passed into the GridItem.
     */
    data: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.shape({

        /**
         * The CSS class name of the grid button.
         */
        className: PropTypes.string,

        /**
         * Override the styles of the grid button.
         */
        style: PropTypes.object,

        /**
         * The theme of the grid button.
         */
        theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

        /**
         * The text value of the grid button.Type can be string or number.
         */
        value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

        /**
         * The grid item's display text. Type can be string, number or bool.
         */
        text: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

        /**
         * The desc value of the grid button. Type can be string or number.
         */
        desc: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

        /**
         * If true,the grid item will be disabled.
         */
        disabled: PropTypes.bool,

        /**
         * If true,the button will be have loading effect.
         */
        isLoading: PropTypes.bool,

        /**
         * If true,the element's ripple effect will be disabled.
         */
        disableTouchRipple: PropTypes.bool,

        /**
         * Use this property to display an icon. It will display on the left.
         */
        iconCls: PropTypes.string,

        /**
         * Use this property to display an icon. It will display on the right.
         */
        rightIconCls: PropTypes.string,

        /**
         * The message of tip.
         */
        tip: PropTypes.string,

        /**
         * The position of tip.
         */
        tipPosition: PropTypes.oneOf(Util.enumerateValue(Tip.Position)),

        /**
         * If true,the item will have center displayed ripple effect.
         */
        rippleDisplayCenter: PropTypes.bool,

        /**
         * You can create a complicated renderer callback instead of value and desc prop.
         */
        itemRenderer: PropTypes.func,

        /**
         * Callback function fired when a grid item touch-tapped.
         */
        onClick: PropTypes.func

    }), PropTypes.string, PropTypes.number, PropTypes.symbol])),

    value: PropTypes.any,

    /**
     * The id field name in data. (default: "id")
     */
    idField: PropTypes.string,

    /**
     * The value field name in data. (default: "value")
     */
    valueField: PropTypes.string,

    /**
     * The display field name in data. (default: "text")
     */
    displayField: PropTypes.string,

    /**
     * The description field name in data. (default: "desc")
     */
    descriptionField: PropTypes.string,

    /**
     * If true, the grid will be disabled.
     */
    disabled: PropTypes.bool,

    /**
     * If true, the grid will be at loading status.
     */
    isLoading: PropTypes.bool,

    shouldPreventContainerScroll: PropTypes.bool,

    radioUncheckedIconCls: PropTypes.string,
    radioCheckedIconCls: PropTypes.string,
    checkboxUncheckedIconCls: PropTypes.string,
    checkboxCheckedIconCls: PropTypes.string,
    checkboxIndeterminateIconCls: PropTypes.string,

    col: PropTypes.number,

    /**
     * You can create a complicated renderer callback instead of value and desc prop.
     */
    renderer: PropTypes.func,

    /**
     * Callback function fired when the grid-item touch tap.
     */
    onItemClick: PropTypes.func,

    /**
     * Callback function fired when the grid-item select.
     */
    onItemSelect: PropTypes.func,

    /**
     * Callback function fired when the grid-item deselect.
     */
    onItemDeselect: PropTypes.func,

    /**
     * Callback function fired when the grid changed.
     */
    onChange: PropTypes.func,

    /**
     * Callback function fired when wrapper wheeled.
     */
    onWheel: PropTypes.func

};

Grid.defaultProps = {

    theme: Theme.DEFAULT,

    selectTheme: Theme.DEFAULT,
    selectMode: SelectMode.SINGLE_SELECT,

    idField: 'id',
    valueField: 'value',
    displayField: 'text',
    descriptionField: 'desc',
    disabled: false,
    shouldPreventContainerScroll: true,

    checkboxUncheckedIconCls: 'far fa-square',
    checkboxCheckedIconCls: 'fas fa-check-square',
    checkboxIndeterminateIconCls: 'fas fa-minus-square',

    col: 3

};

export default Grid;<commit_msg>update Grid<commit_after>/**
 * @file Grid component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import isArray from 'lodash/isArray';
import classNames from 'classnames';

import GridItem from '../_GridItem';
import Tip from '../Tip';
import Theme from '../Theme';

import SelectMode from '../_statics/SelectMode';

import Util from '../_vendors/Util';
import Event from '../_vendors/Event';
import Calculation from '../_vendors/Calculation';

class Grid extends Component {

    static SelectMode = SelectMode;
    static Theme = Theme;

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.state = {
            value: Calculation.getInitValue(props)
        };

    }

    listItemSelectHandler = (item, index) => {

        const {selectMode} = this.props;

        let {value} = this.state;

        if (selectMode === SelectMode.MULTI_SELECT) {

            if (!value || !isArray(value)) {
                value = [];
            }

            value.push(item);

        } else if (selectMode === SelectMode.SINGLE_SELECT) {
            value = item;
        }

        this.setState({
            value
        }, () => {
            const {onItemSelect, onChange} = this.props;
            onItemSelect && onItemSelect(item, index);
            onChange && onChange(value, index);
        });

    };

    listItemDeselectHandler = (item, index) => {

        const {selectMode} = this.props;

        if (selectMode !== SelectMode.MULTI_SELECT) {
            return;
        }

        const {valueField, displayField} = this.props;
        let {value} = this.state;

        if (!value || !isArray(value)) {
            value = [];
        } else {
            value = value.filter(valueItem => {
                return Util.getValueByValueField(valueItem, valueField, displayField)
                    != Util.getValueByValueField(item, valueField, displayField);
            });
        }

        this.setState({
            value
        }, () => {
            const {onItemDeselect, onChange} = this.props;
            onItemDeselect && onItemDeselect(item, index);
            onChange && onChange(value, index);
        });

    };

    componentWillReceiveProps(nextProps) {
        if (nextProps.value !== this.state.value) {
            this.setState({
                value: Calculation.getInitValue(nextProps)
            });
        }
    }

    renderGridItem = (item, index) => {

        if (!item) {
            return;
        }

        const {

                theme, itemHeight, col,

                selectTheme, selectMode, radioUncheckedIconCls, radioCheckedIconCls,
                checkboxUncheckedIconCls, checkboxCheckedIconCls, checkboxIndeterminateIconCls,

                idField, valueField, displayField, descriptionField, disabled, isLoading, renderer, onItemClick,
                parentEl

            } = this.props,
            {value} = this.state;

        return typeof item === 'object' ?
            (
                <GridItem key={(idField in item && item[idField]) || index}
                          {...item}
                          index={index}
                          style={{height: itemHeight}}
                          theme={item.theme || theme}
                          parentEl={parentEl}
                          col={col}
                          selectTheme={item.selectTheme || selectTheme}
                          radioUncheckedIconCls={item.radioUncheckedIconCls || radioUncheckedIconCls}
                          radioCheckedIconCls={item.radioCheckedIconCls || radioCheckedIconCls}
                          checkboxUncheckedIconCls={item.checkboxUncheckedIconCls || checkboxUncheckedIconCls}
                          checkboxCheckedIconCls={item.checkboxCheckedIconCls || checkboxCheckedIconCls}
                          checkboxIndeterminateIconCls={item.checkboxIndeterminateIconCls || checkboxIndeterminateIconCls}
                          data={item}
                          checked={Calculation.isItemChecked(item, value, this.props)}
                          value={Util.getValueByValueField(item, valueField, displayField)}
                          text={Util.getTextByDisplayField(item, displayField, valueField)}
                          desc={item[descriptionField] || null}
                          disabled={disabled || item.disabled}
                          isLoading={isLoading || item.isLoading}
                          selectMode={selectMode}
                          renderer={renderer}
                          onClick={e => {
                              onItemClick && onItemClick(item, index, e);
                              item.onClick && item.onClick(e);
                          }}
                          onSelect={() => {
                              this.listItemSelectHandler(item, index);
                          }}
                          onDeselect={() => {
                              this.listItemDeselectHandler(item, index);
                          }}/>
            )
            :
            (
                <GridItem key={index}
                          index={index}
                          style={{height: itemHeight}}
                          theme={item.theme || theme}
                          parentEl={parentEl}
                          col={col}
                          selectTheme={item.selectTheme || selectTheme}
                          radioUncheckedIconCls={item.radioUncheckedIconCls || radioUncheckedIconCls}
                          radioCheckedIconCls={item.radioCheckedIconCls || radioCheckedIconCls}
                          checkboxUncheckedIconCls={item.checkboxUncheckedIconCls || checkboxUncheckedIconCls}
                          checkboxCheckedIconCls={item.checkboxCheckedIconCls || checkboxCheckedIconCls}
                          checkboxIndeterminateIconCls={item.checkboxIndeterminateIconCls || checkboxIndeterminateIconCls}
                          data={item}
                          checked={Calculation.isItemChecked(item, value, this.props)}
                          value={item}
                          text={item}
                          disabled={disabled}
                          isLoading={isLoading}
                          selectMode={selectMode}
                          renderer={renderer}
                          onClick={e => {
                              onItemClick && onItemClick(item, index, e);
                          }}
                          onSelect={() => {
                              this.listItemSelectHandler(item, index);
                          }}
                          onDeselect={() => {
                              this.listItemDeselectHandler(item, index);
                          }}/>
            );

    };

    render() {

        const {children, className, style, data, disabled} = this.props,

            gridClassName = classNames('grid', {
                [className]: className
            });

        return (

 Event.wheelHandler(e, this.props)}>

                {data && data.map((item, index) => this.renderGridItem(item, index))}

                {children}

        );
    }
}

Grid.propTypes = {

    /**
     * The CSS class name of the root element.
     */
    className: PropTypes.string,

    /**
     * Override the styles of the root element.
     */
    style: PropTypes.object,

    /**
     * The theme of the grid item.
     */
    theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    /**
     * The theme of the grid item select radio or checkbox.
     */
    selectTheme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    /**
     * The mode of listItem.
     */
    selectMode: PropTypes.oneOf(Util.enumerateValue(SelectMode)),

    /**
     * Children passed into the GridItem.
     */
    data: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.shape({

        /**
         * The CSS class name of the grid button.
         */
        className: PropTypes.string,

        /**
         * Override the styles of the grid button.
         */
        style: PropTypes.object,

        /**
         * The theme of the grid button.
         */
        theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

        /**
         * The text value of the grid button.Type can be string or number.
         */
        value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

        /**
         * The grid item's display text. Type can be string, number or bool.
         */
        text: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

        /**
         * The desc value of the grid button. Type can be string or number.
         */
        desc: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

        /**
         * If true,the grid item will be disabled.
         */
        disabled: PropTypes.bool,

        /**
         * If true,the button will be have loading effect.
         */
        isLoading: PropTypes.bool,

        /**
         * If true,the element's ripple effect will be disabled.
         */
        disableTouchRipple: PropTypes.bool,

        /**
         * Use this property to display an icon. It will display on the left.
         */
        iconCls: PropTypes.string,

        /**
         * Use this property to display an icon. It will display on the right.
         */
        rightIconCls: PropTypes.string,

        /**
         * The message of tip.
         */
        tip: PropTypes.string,

        /**
         * The position of tip.
         */
        tipPosition: PropTypes.oneOf(Util.enumerateValue(Tip.Position)),

        /**
         * If true,the item will have center displayed ripple effect.
         */
        rippleDisplayCenter: PropTypes.bool,

        /**
         * You can create a complicated renderer callback instead of value and desc prop.
         */
        itemRenderer: PropTypes.func,

        /**
         * Callback function fired when a grid item touch-tapped.
         */
        onClick: PropTypes.func

    }), PropTypes.string, PropTypes.number, PropTypes.symbol])),

    value: PropTypes.any,

    /**
     * The id field name in data. (default: "id")
     */
    idField: PropTypes.string,

    /**
     * The value field name in data. (default: "value")
     */
    valueField: PropTypes.string,

    /**
     * The display field name in data. (default: "text")
     */
    displayField: PropTypes.string,

    /**
     * The description field name in data. (default: "desc")
     */
    descriptionField: PropTypes.string,

    /**
     * If true, the grid will be disabled.
     */
    disabled: PropTypes.bool,

    /**
     * If true, the grid will be at loading status.
     */
    isLoading: PropTypes.bool,

    radioUncheckedIconCls: PropTypes.string,
    radioCheckedIconCls: PropTypes.string,
    checkboxUncheckedIconCls: PropTypes.string,
    checkboxCheckedIconCls: PropTypes.string,
    checkboxIndeterminateIconCls: PropTypes.string,

    col: PropTypes.number,

    /**
     * You can create a complicated renderer callback instead of value and desc prop.
     */
    renderer: PropTypes.func,

    /**
     * Callback function fired when the grid-item touch tap.
     */
    onItemClick: PropTypes.func,

    /**
     * Callback function fired when the grid-item select.
     */
    onItemSelect: PropTypes.func,

    /**
     * Callback function fired when the grid-item deselect.
     */
    onItemDeselect: PropTypes.func,

    /**
     * Callback function fired when the grid changed.
     */
    onChange: PropTypes.func,

    /**
     * Callback function fired when wrapper wheeled.
     */
    onWheel: PropTypes.func

};

Grid.defaultProps = {

    theme: Theme.DEFAULT,

    selectTheme: Theme.DEFAULT,
    selectMode: SelectMode.SINGLE_SELECT,

    idField: 'id',
    valueField: 'value',
    displayField: 'text',
    descriptionField: 'desc',
    disabled: false,

    checkboxUncheckedIconCls: 'far fa-square',
    checkboxCheckedIconCls: 'fas fa-check-square',
    checkboxIndeterminateIconCls: 'fas fa-minus-square',

    col: 3

};

export default Grid;
<|endoftext|>


<commit_before>casper.on('remote.message', function(message) {
    this.echo('Message: '+ JSON.stringify(message));
});

casper.on('page.error', function(message, trace) {
    this.echo('Page error: ' + message);
    this.echo(JSON.stringify(trace));
});

casper.on('page.initialized', function() {
    this.evaluate(polyfillObjectAssign);
    this.evaluate(polyfillFunctionBind);
});

var polyfillFunctionBind = function() {
    if (!Function.prototype.bind) {
        Function.prototype.bind = function(oThis) {
            if (typeof this !== 'function') {
                // closest thing possible to the ECMAScript 5
                // internal IsCallable function
                throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
            }

            var aArgs   = Array.prototype.slice.call(arguments, 1),
                fToBind = this,
                fNOP    = function() {},
                fBound  = function() {
                    return fToBind.apply(this instanceof fNOP
                            ? this
                            : oThis,
                        aArgs.concat(Array.prototype.slice.call(arguments)));
                };

            if (this.prototype) {
                // native functions don't have a prototype
                fNOP.prototype = this.prototype;
            }
            fBound.prototype = new fNOP();

            return fBound;
        };
    }
    console.log("Function.prototype.bind has been polyfilled");
};

var polyfillObjectAssign = function() {
    if (typeof Object.assign != 'function') {
          Object.assign = function (target) {
          'use strict';
          if (target === undefined || target === null) {
            throw new TypeError('Cannot convert undefined or null to object');
          }

          var output = Object(target);
          for (var index = 1; index < arguments.length; index++) {
            var source = arguments[index];
            if (source !== undefined && source !== null) {
              for (var nextKey in source) {
                if (source.hasOwnProperty(nextKey)) {
                  output[nextKey] = source[nextKey];
                }
              }
            }
          }
          return output;
        };
    }
    console.log("Object.assign has been polyfilled");
};
<commit_msg>Add array find polypill<commit_after>casper.on('remote.message', function(message) {
    this.echo('Message: '+ JSON.stringify(message));
});

casper.on('page.error', function(message, trace) {
    this.echo('Page error: ' + message);
    this.echo(JSON.stringify(trace));
});

casper.on('page.initialized', function() {
    this.evaluate(polyfillObjectAssign);
    this.evaluate(polyfillFunctionBind);
    this.evaluate(polyfillArrayFind);
});

var polyfillFunctionBind = function() {
    if (!Function.prototype.bind) {
        Function.prototype.bind = function(oThis) {
            if (typeof this !== 'function') {
                // closest thing possible to the ECMAScript 5
                // internal IsCallable function
                throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
            }

            var aArgs   = Array.prototype.slice.call(arguments, 1),
                fToBind = this,
                fNOP    = function() {},
                fBound  = function() {
                    return fToBind.apply(this instanceof fNOP
                            ? this
                            : oThis,
                        aArgs.concat(Array.prototype.slice.call(arguments)));
                };

            if (this.prototype) {
                // native functions don't have a prototype
                fNOP.prototype = this.prototype;
            }
            fBound.prototype = new fNOP();

            return fBound;
        };
    }
};

var polyfillObjectAssign = function() {
    if (typeof Object.assign != 'function') {
          Object.assign = function (target) {
          'use strict';
          if (target === undefined || target === null) {
            throw new TypeError('Cannot convert undefined or null to object');
          }

          var output = Object(target);
          for (var index = 1; index < arguments.length; index++) {
            var source = arguments[index];
            if (source !== undefined && source !== null) {
              for (var nextKey in source) {
                if (source.hasOwnProperty(nextKey)) {
                  output[nextKey] = source[nextKey];
                }
              }
            }
          }
          return output;
        };
    }
};

var polyfillArrayFind = function() {
  if (!Array.prototype.find) {
    Array.prototype.find = function(predicate) {
      if (this === null) {
        throw new TypeError('Array.prototype.find called on null or undefined');
      }
      if (typeof predicate !== 'function') {
        throw new TypeError('predicate must be a function');
      }
      var list = Object(this);
      var length = list.length >>> 0;
      var thisArg = arguments[1];
      var value;

      for (var i = 0; i < length; i++) {
        value = list[i];
        if (predicate.call(thisArg, value, i, list)) {
          return value;
        }
      }
      return undefined;
    };
  }
}
<|endoftext|>