-1

[Context]

I'm trying to build a RailRoadsOnline Train Builder website. Where you can select you locomotive and wagons. Then it will calculate if you can make certain grades or not. I'm planning on adding more features later but basics first. This is my first project I'm doing with HTML/JS. I do have experience in C/C++/Py/VHDL.

[Problem]

I'm having issues generating a dropdown list generated by a button, this to have the amount of locs/wagons dynamic and not hard coded. When I pres the button I want it to make a dropdown list which gets its content list from an object. The dropdown list must show the Display_Name value but return the key when read from. Object example (Abstract/example value){Important_Key_Name_I_Need_Later: {Display_Name: "The Name", ...}, Porter_1: {Display_Name: "Porter 1", Mass: 7242, Length: 4, Tractive_Effort: 12.8}, ...} So the when you would select Porter 1 in code it would return/read out as Porter_1

I've adapted my code from this this which worked but now that I'm generating the entire dropdown it breaks.

Here is the code, I'm sorry it is a bit much:

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Test Web Page</title>
</head>
<body>
<form>
    <input type="button" id="Calculate" value="Calculate">
    <p> Total Weight</p>
    <p id="Loc_Sel">Selected Loc</p>

    <input type="button" id="Add_Locomotive" value="Add a locomotive">
    <input type="button" id="Add_Wagon" value="Add a wagon">
    <input type="button" id="Reset" value="Reset">

</form>
</script>
<div id="Button_Div"></div>
<script type="text/javascript">
    //Locomotives
    let Locomotives = {
        Porter_1: {Mass: 7242, Length: 4, Tractive_Effort: 12.8, Display_Name: "Porter 1"},
        Porter_2: {Mass: 8145, Length: 4.8, Tractive_Effort: 12.8, Display_Name: "Porter 2"},
        Montezuma: {Mass: 14969, Length: 11, Tractive_Effort: 16.2, Display_Name: "Montezuma"},
        Shay: {Mass: 21754, Length: 8.1, Tractive_Effort: 26.2, Display_Name: "Shay"},
        Tenmile: {Mass: 19848, Length: 11.8, Tractive_Effort: 37.7, Display_Name: "Tenmile"},
        Eureka: {Mass:17164 , Length: 13.1, Tractive_Effort: 25, Display_Name: "Eureka"},
        Glenbrook: {Mass: 17128, Length: 13.5, Tractive_Effort: 34.1, Display_Name: "Glenbrook"},
        DRGW_Class_48: {Mass: 14969, Length: 7.7, Tractive_Effort: 41.7, Display_Name: "D&RGW Class 48"},
        Mosca: {Mass: 25348, Length: 13.8, Tractive_Effort: 38.3, Display_Name: "Mosca"},
        Cooke_Mogul: {Mass: 26388, Length: 15.1, Tractive_Effort: 52.8, Display_Name: "Cooke Mogul"},
        Heisler: {Mass: 28064, Length: 9.1, Tractive_Effort: 57.9, Display_Name: "Heisler"},
        Ruby_Basin: {Mass: 33042, Length: 10.3, Tractive_Effort: 60.1, Display_Name: "Ruby Basin"},
        Cooke_Consilidation: {Mass: 26706, Length: 15.1, Tractive_Effort: 59.9, Display_Name: "Cooke Consilidation"},
        Climax: {Mass: 24194, Length: 8.7, Tractive_Effort: 76.6, Display_Name: "Climax"},
        DRG_Class_70: {Mass: 15468, Length: 15.6, Tractive_Effort: 68.8, Display_Name: "D&RG Class 70"},
        ETWNC_2_8_0: {Mass: 16545, Length: 16.2, Tractive_Effort: 73.6, Display_Name: "ET&WNC 2-8-0"}
    };
    //Wagons
    let Wagons = {
        No_Car: {Mass: 0, Length: 0, Display_Name: "No_Car", Cargo: {}},
        Skeleton_Car: {Mass: 3000, Length: 6.4, Display_Name: "Skeleton Car", Cargo: {Logs: 5}},
        Flatcar_Rounds: {Mass: 3800, Length: 8.4, Display_Name: "Flatcar Rounds", Cargo: {Logs: 6, Steel_Pipes: 9}},
        Flatcar_Stakes: {Mass: 4000, Length: 8.4, Display_Name: "Flatcar Stakes", Cargo: {Lumber: 6, Beams: 3, Raw_Iron: 3, Rails: 10}},
        Flatcar_Bulkhead: {Mass: 4100, Length: 8.4, Display_Name: "Flatcar Bulkhead", Cargo: {Cordwood: 6, Oil_Barrels: 46}},
        Hopper: {Mass: 6000, Length: 8.2, Display_Name: "Hopper", Cargo: {Iron_ore: 10, Coal_Ore: 10}},
        EBT_Hopper: {Mass: 6000, Length: 7.75, Display_Name: "EBT Hopper", Cargo: {Iron_ore: 8, Coal_Ore: 10}},
        Tanker: {Mass: 13968, Length: 8.1, Display_Name: "Tanker", Cargo: {Crude_Oil: 12}},
        Coffin_Tank_Car: {Mass: 14250, Length: 10, Display_Name: "Coffin Tank Car", Cargo: {Crude_Oil: 8}},
        Box_Car: {Mass: 7938, Length: 8.1, Display_Name: "Box Car", Cargo: {Tool_Crates: 32}},
        Stock_Car: {Mass: 8000, Length: 9.4, Display_Name: "Stock Car", Cargo: {Tool_Crates: 32}},
        Bobber_Caboose: {Mass: 5400, Length: 6.8, Display_Name: "Bobber Caboose", Cargo: {}},
        DSPP_Waycar: {Mass: 5000, Length: 6.1, Display_Name: "DSP&P Waycar", Cargo: {}},
        Snow_Plow: {Mass: 6000, Length: 6, Display_Name: "Snow Plow", Cargo: {}}
    };
    //Cargo Types
    let Cargo_Types = {
        Logs: {Money: 10, Mass: 2000, Display_Name: "Logs"},
        Cordwood: {Money: 10, Mass: 1200, Display_Name: "Cordwood"},
        Beams: {Money: 24, Mass: 1410, Display_Name: "Beams"},
        Lumber: {Money: 12, Mass: 1350, Display_Name: "Lumber"},
        Iron_Ore: {Money: 20, Mass: 1000, Display_Name: "Iron Ore"},
        Coal_Ore: {Money: 15, Mass: 1000, Display_Name: "Coal Ore"},
        Raw_Iron: {Money: 25, Mass: 1490, Display_Name: "Raw Iron"},
        Rails: {Money: 25, Mass: 900, Display_Name: "Rails"},
        Steel_Pipes: {Money: 40, Mass: 1800, Display_Name: "Steel Pipes"},
        Tool_Crates: {Money: 30, Mass: 100, Display_Name: "Tool Crates"},
        Crude_Oil: {Money: 25, Mass: 1000, Display_Name: "Crude Oil"},
        Oil_Barrels: {Money: 40, Mass: 137, Display_Name: "Oil Barrels"}
    };

    let Vehicles = {Loc: -1, Wag: -1};
    
    const Button_Calculate = document.getElementById('Calculate');
    const Button_Add_Locomotive = document.getElementById('Add_Locomotive');
    const Button_Add_Wagon = document.getElementById('Add_Wagon');
    const Button_Reset = document.getElementById('Reset');
    
    //This is the adapted version of the linked code.
    function Mk_DropDown(Id, Dictionairy){
        var Options = document.getElementById(Id);
        Object.keys(Dictionairy).forEach(key => {
            Options.appendChild(new Option(Dictionairy[key]['Display_Name'], key));
        });
    }
    

    //This function should not becausing issues, just included for completeness.
    function changeText(element, value) {
        document.getElementById(element).innerHTML = value;
    }
    
    //This function should not becausing issues, just included for completeness.
    function Calculate_Train_Weight() {
        let Weight = 0;
        for(let i = 0; i < Vehicles['Loc']; i++) {
            Locomotives[document.getElementById("Select_Loc_" + i).value]['Mass'];
        }
        for(let i = 0; i < Vehicles['Wag']; i++) {
            Wagons[document.getElementById("Select_Wagon_" + i).value]['Mass'];
        }
        return Weight;
    }

    //This function should not becausing issues, just included for completeness.
    function Calculate() {
        changeText('Loc_Sel', Calculate_Train_Weight());
    }

    function Add_Locomotive() {
        Vehicles['Loc']++;
        console.log(Vehicles['Loc']);
        document.getElementById("Button_Div").innerHTML = "<select> id=\"Select_Locomotive_" + Vehicles['Loc'] + "<option>Choose a locomotive</option></select>" + document.getElementById("Button_Div").innerHTML
//Is doing it once when creating new box enough or should it loop through al existing dropdowns as well?
        Mk_DropDown("Select_Loc_" + Vehicles['Loc'], Locomotives); 
    }

    function Add_Wagon() {
        Vehicles['Wag']++;
        document.getElementById("Button_Div").innerHTML += "<select> id=\"Select_Wagon_" + Vehicles['Wag'] + "<option>Choose a wagon</option></select>"
//Is doing it once when creating new box enough or should it loop through al existing dropdowns as well?
        for(let i = 0; i < Vehicles['Wag']; i++) {
            Mk_DropDown("Select_Wagon_" + i, Wagons); 
        }
    }

    //This function works but feels like a hack, so if you have suggestions for this feel free to mention it!
    function Reset() {
        document.getElementById("Button_Div").innerHTML = "";
        Vehicles['Loc'] = 0;
        Vehicles['Wag'] = 0;
    }

    Button_Add_Locomotive.addEventListener('click', Add_Locomotive);
    Button_Add_Wagon.addEventListener('click', Add_Wagon);
    Button_Reset.addEventListener('click', Reset);
    Button_Calculate.addEventListener('click', Calculate);
</script>
</body>
</html>
Mark Schultheiss
  • 32,614
  • 12
  • 69
  • 100
OADINC
  • 27
  • 8
  • 1
    "Breaks" in what way? In the browser's development tools, are there any errors on the console? When you use the browser's script debugger, which operation first fails or produces an unexpected result? – David Jun 23 '23 at 18:37
  • 1
    Your HTML as posted is invalid – Mark Schultheiss Jun 23 '23 at 19:28

1 Answers1

1

Honestly this implementation is a little jank, but your main problem is the way you're concatenating strings. You're concatenating the strings in a way that makes Mk_DropDown unable to find the element with the id you've passed.

In Add_Locomotive you need to pass an id that's consistent with what you're assigning to the select elements. You've assigned the ids to iterate for each button press with the prefix Select_Locomotive_[someindex]. I'm going to change that to Select_Loc_[someindex] like what's passed into Mk_DropDown.

The second problem is that the id attribute needs to be inside the leading select tag.

The third problem is that the backslash exception needs to close the id as well as start.

A revised Add_Locomotive could look like:

function Add_Locomotive() {
        Vehicles['Loc']++;
        console.log(Vehicles['Loc']);
        document.getElementById("Button_Div").innerHTML = "<select " + "id=\"Select_Loc_" + Vehicles['Loc'] + "\" >" + "<option>Choose a locomotive</option></select>" + document.getElementById("Button_Div").innerHTML
        Mk_DropDown("Select_Loc_" + Vehicles['Loc'], Locomotives);
}

To simplify further I'd use string interpolation:

function Add_Locomotive() {
        Vehicles['Loc']++;
        console.log(Vehicles['Loc']);
        document.getElementById("Button_Div").innerHTML = `<select id="Select_Loc_${Vehicles['Loc']}" > <option>Choose a locomotive</option></select>` + document.getElementById("Button_Div").innerHTML
        Mk_DropDown("Select_Loc_" + Vehicles['Loc'], Locomotives);
}

I can't tell if you're just trying stuff out in your implementation for Add_Wagon or if you're testing performance, but you need to change your equality check in the for loop to i <= Vehicles['Wag'] or it won't run because adding the first wagon will make Vehicles['Wag'] === 0, which is not less than 0 and your loop only runs when i < 0 .

function Add_Wagon() {
        Vehicles['Wag']++;
        document.getElementById("Button_Div").innerHTML += `<select id="Select_Wagon_${Vehicles['Wag']}" > <option>Choose a wagon</option></select>`
        for (let i = 0; i <= Vehicles['Wag']; i++) {
            Mk_DropDown("Select_Wagon_" + i, Wagons);
        }
}

Lastly, your Reset function will break the Add_Wagon function because it resets the Vehicles object to {"Wag": 0, "Loc": 0} which is not a reset since it was initiated with {"Wag": -1, "Loc": -1}. Doing the loop in Add_Wag won't work because you will run Reset, run Add_Wagon and append a child with id="Select_Wagon_1" then you will run the loop and the first argument to be passed to Mk_DropDown is Mk_DropDown("Select_Wagon_0", Wagons) which is not a child in the DOM.

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Test Web Page</title>
</head>

<body>
    <form>
        <input type="button" id="Calculate" value="Calculate">
        <p> Total Weight</p>
        <p id="Loc_Sel">Selected Loc</p>

        <input type="button" id="Add_Locomotive" value="Add a locomotive">
        <input type="button" id="Add_Wagon" value="Add a wagon">
        <input type="button" id="Reset" value="Reset">

    </form>
    </script>
    <div id="Button_Div"></div>
    <script type="text/javascript">
        //Locomotives
        let Locomotives = {
            Porter_1: { Mass: 7242, Length: 4, Tractive_Effort: 12.8, Display_Name: "Porter 1" },
            Porter_2: { Mass: 8145, Length: 4.8, Tractive_Effort: 12.8, Display_Name: "Porter 2" },
            Montezuma: { Mass: 14969, Length: 11, Tractive_Effort: 16.2, Display_Name: "Montezuma" },
            Shay: { Mass: 21754, Length: 8.1, Tractive_Effort: 26.2, Display_Name: "Shay" },
            Tenmile: { Mass: 19848, Length: 11.8, Tractive_Effort: 37.7, Display_Name: "Tenmile" },
            Eureka: { Mass: 17164, Length: 13.1, Tractive_Effort: 25, Display_Name: "Eureka" },
            Glenbrook: { Mass: 17128, Length: 13.5, Tractive_Effort: 34.1, Display_Name: "Glenbrook" },
            DRGW_Class_48: { Mass: 14969, Length: 7.7, Tractive_Effort: 41.7, Display_Name: "D&RGW Class 48" },
            Mosca: { Mass: 25348, Length: 13.8, Tractive_Effort: 38.3, Display_Name: "Mosca" },
            Cooke_Mogul: { Mass: 26388, Length: 15.1, Tractive_Effort: 52.8, Display_Name: "Cooke Mogul" },
            Heisler: { Mass: 28064, Length: 9.1, Tractive_Effort: 57.9, Display_Name: "Heisler" },
            Ruby_Basin: { Mass: 33042, Length: 10.3, Tractive_Effort: 60.1, Display_Name: "Ruby Basin" },
            Cooke_Consilidation: { Mass: 26706, Length: 15.1, Tractive_Effort: 59.9, Display_Name: "Cooke Consilidation" },
            Climax: { Mass: 24194, Length: 8.7, Tractive_Effort: 76.6, Display_Name: "Climax" },
            DRG_Class_70: { Mass: 15468, Length: 15.6, Tractive_Effort: 68.8, Display_Name: "D&RG Class 70" },
            ETWNC_2_8_0: { Mass: 16545, Length: 16.2, Tractive_Effort: 73.6, Display_Name: "ET&WNC 2-8-0" }
        };
        //Wagons
        let Wagons = {
            No_Car: { Mass: 0, Length: 0, Display_Name: "No_Car", Cargo: {} },
            Skeleton_Car: { Mass: 3000, Length: 6.4, Display_Name: "Skeleton Car", Cargo: { Logs: 5 } },
            Flatcar_Rounds: { Mass: 3800, Length: 8.4, Display_Name: "Flatcar Rounds", Cargo: { Logs: 6, Steel_Pipes: 9 } },
            Flatcar_Stakes: { Mass: 4000, Length: 8.4, Display_Name: "Flatcar Stakes", Cargo: { Lumber: 6, Beams: 3, Raw_Iron: 3, Rails: 10 } },
            Flatcar_Bulkhead: { Mass: 4100, Length: 8.4, Display_Name: "Flatcar Bulkhead", Cargo: { Cordwood: 6, Oil_Barrels: 46 } },
            Hopper: { Mass: 6000, Length: 8.2, Display_Name: "Hopper", Cargo: { Iron_ore: 10, Coal_Ore: 10 } },
            EBT_Hopper: { Mass: 6000, Length: 7.75, Display_Name: "EBT Hopper", Cargo: { Iron_ore: 8, Coal_Ore: 10 } },
            Tanker: { Mass: 13968, Length: 8.1, Display_Name: "Tanker", Cargo: { Crude_Oil: 12 } },
            Coffin_Tank_Car: { Mass: 14250, Length: 10, Display_Name: "Coffin Tank Car", Cargo: { Crude_Oil: 8 } },
            Box_Car: { Mass: 7938, Length: 8.1, Display_Name: "Box Car", Cargo: { Tool_Crates: 32 } },
            Stock_Car: { Mass: 8000, Length: 9.4, Display_Name: "Stock Car", Cargo: { Tool_Crates: 32 } },
            Bobber_Caboose: { Mass: 5400, Length: 6.8, Display_Name: "Bobber Caboose", Cargo: {} },
            DSPP_Waycar: { Mass: 5000, Length: 6.1, Display_Name: "DSP&P Waycar", Cargo: {} },
            Snow_Plow: { Mass: 6000, Length: 6, Display_Name: "Snow Plow", Cargo: {} }
        };
        //Cargo Types
        let Cargo_Types = {
            Logs: { Money: 10, Mass: 2000, Display_Name: "Logs" },
            Cordwood: { Money: 10, Mass: 1200, Display_Name: "Cordwood" },
            Beams: { Money: 24, Mass: 1410, Display_Name: "Beams" },
            Lumber: { Money: 12, Mass: 1350, Display_Name: "Lumber" },
            Iron_Ore: { Money: 20, Mass: 1000, Display_Name: "Iron Ore" },
            Coal_Ore: { Money: 15, Mass: 1000, Display_Name: "Coal Ore" },
            Raw_Iron: { Money: 25, Mass: 1490, Display_Name: "Raw Iron" },
            Rails: { Money: 25, Mass: 900, Display_Name: "Rails" },
            Steel_Pipes: { Money: 40, Mass: 1800, Display_Name: "Steel Pipes" },
            Tool_Crates: { Money: 30, Mass: 100, Display_Name: "Tool Crates" },
            Crude_Oil: { Money: 25, Mass: 1000, Display_Name: "Crude Oil" },
            Oil_Barrels: { Money: 40, Mass: 137, Display_Name: "Oil Barrels" }
        };

        let Vehicles = { Loc: -1, Wag: -1 };

        const Button_Calculate = document.getElementById('Calculate');
        const Button_Add_Locomotive = document.getElementById('Add_Locomotive');
        const Button_Add_Wagon = document.getElementById('Add_Wagon');
        const Button_Reset = document.getElementById('Reset');

        //This is the adapted version of the linked code.
        function Mk_DropDown(Id, Dictionairy) {
            var Options = document.getElementById(Id);
            Object.keys(Dictionairy).forEach(key => {
                Options.appendChild(new Option(Dictionairy[key]['Display_Name'], key));
            });
        }


        //This function should not becausing issues, just included for completeness.
        function changeText(element, value) {
            document.getElementById(element).innerHTML = value;
        }

        //This function should not becausing issues, just included for completeness.
        function Calculate_Train_Weight() {
            let Weight = 0;
            for (let i = 0; i < Vehicles['Loc']; i++) {
                Locomotives[document.getElementById("Select_Loc_" + i).value]['Mass'];
            }
            for (let i = 0; i < Vehicles['Wag']; i++) {
                Wagons[document.getElementById("Select_Wagon_" + i).value]['Mass'];
            }
            return Weight;
        }

        //This function should not becausing issues, just included for completeness.
        function Calculate() {
            changeText('Loc_Sel', Calculate_Train_Weight());
        }

        function Add_Locomotive() {
            Vehicles['Loc']++;
            console.log(Vehicles['Loc']);
            document.getElementById("Button_Div").innerHTML = `<select id="Select_Loc_${Vehicles['Loc']}" > <option>Choose a locomotive</option></select>` + document.getElementById("Button_Div").innerHTML
            //Is doing it once when creating new box enough or should it loop through al existing dropdowns as well?
            Mk_DropDown("Select_Loc_" + Vehicles['Loc'], Locomotives);
        }

        function Add_Wagon() {
            Vehicles['Wag']++;
            document.getElementById("Button_Div").innerHTML += `<select id="Select_Wagon_${Vehicles['Wag']}" > <option>Choose a wagon</option></select>`
            //Is doing it once when creating new box enough or should it loop through al existing dropdowns as well?
            for (let i = 0; i <= Vehicles['Wag']; i++) {
                console.log(i)
                Mk_DropDown("Select_Wagon_" + i, Wagons);
            }
        }

        //This function works but feels like a hack, so if you have suggestions for this feel free to mention it!
        function Reset() {
            document.getElementById("Button_Div").innerHTML = "";
            Vehicles['Loc'] = -1;
            Vehicles['Wag'] = -1;
        }

        Button_Add_Locomotive.addEventListener('click', Add_Locomotive);
        Button_Add_Wagon.addEventListener('click', Add_Wagon);
        Button_Reset.addEventListener('click', Reset);
        Button_Calculate.addEventListener('click', Calculate);
    </script>
</body>

</html>
Eyas Valdez
  • 135
  • 5
  • I didn't remove any of the console logs in the code so the console takes up a large part of the section. If you want to run the code snippet I'd recommend clicking the full page option. – Eyas Valdez Jun 23 '23 at 19:29
  • Thanks this is great! How would you implement this in a way which is less jank? I'm trying to learn to code less janky. Also would there be an easy way to keep the selected vehicles when adding another vehicle (Loc/Wag)? – OADINC Jun 23 '23 at 20:09
  • Also yes I was testing a different implementation in Add_Wagon. – OADINC Jun 23 '23 at 20:14