Pārlūkot izejas kodu

Improve code structure and form element update events

Shiv Tyagi 3 gadi atpakaļ
vecāks
revīzija
4f1323f743
1 mainītis faili ar 209 papildinājumiem un 112 dzēšanām
  1. 209 112
      templates/index.html

+ 209 - 112
templates/index.html

@@ -37,7 +37,7 @@
         <form action="/generate" method="post">
             <div id="vehicle_list">
                 <label for="vehicle">Choose a vehicle:
-                    <select name="vehicle" id="vehicle" onchange="requestBranches(this.value);">
+                    <select name="vehicle" id="vehicle" onchange="onVehicleChange(this.value);">
                         {% for vehicle in get_vehicle_names() %}
                         <option value="{{vehicle}}" {% if vehicle == get_default_vehicle_name() %} selected {% endif %}>{{vehicle}}</option>
                         {% endfor %}
@@ -51,8 +51,10 @@
             <p></p>
             <div id="build_options"></div>
             <br>
-            <input type="submit" value="Generate" id="submit" disabled>
-            <input type="button" value="Reset option defaults" id="reset_def" onclick="requestDefaults();">
+            <div id="message" style="color:red"></div>
+            <br>
+            <input type="submit" value="Generate" id="submit">
+            <input type="button" value="Reset option defaults" id="reset_def" onclick="Features.applyDefaults();">
         </form>
     </div>
     <hr>
@@ -60,12 +62,184 @@
     <div id="build_status"></div>
     <br/>
     <script>
-        // this object contains defines and their corresponding labels for build options
-        var define_labels = {};
+
+        const Features = (() => {
+            let features = {};
+            let features_dictionary = {};
+
+            function resetDictionary() {
+                features_dictionary = {};
+                features.forEach((category, cat_idx) => {
+                    category['options'].forEach((option, opt_idx) => {
+                        features_dictionary[option.define] = {
+                            'category_index' : cat_idx,
+                            'option_index' : opt_idx,
+                        };
+                    });
+                });
+            }
+
+            function reset(new_features) {
+                features = new_features;
+                resetDictionary();
+            }
+
+            function getByDefine(define) {
+                let dict_value = features_dictionary[define];
+                if (dict_value == undefined) {
+                    return null;
+                }
+                return features[dict_value['category_index']]['options'][dict_value['option_index']];
+            }
+
+            function updateDefaults(defines_array) {
+                // updates default on the basis of define array passed
+                // the define array consists define in format, EXAMPLE_DEFINE or !EXAMPLE_DEFINE
+                // we update the defaults in features object by processing those defines
+                for (let i=0; i<defines_array.length; i++) {
+                    let select_opt = (defines_array[i][0] != '!');
+                    let sanitised_define = (select_opt ? defines_array[i] : defines_array[i].substring(1)); // this removes the leading '!' from define if it contatins
+                    if (getByDefine(sanitised_define)) {
+                        let cat_idx = features_dictionary[sanitised_define]['category_index'];
+                        let opt_idx = features_dictionary[sanitised_define]['option_index'];
+                        getByDefine(sanitised_define).default = select_opt ? 1 : 0;
+                    }
+                }
+            }
+
+            function applyDefaults() {
+                features.forEach(category => {
+                    category['options'].forEach(option => {
+                        element = document.getElementById(option['label']);
+                        if (element != undefined) {
+                            element.checked = (option['default'] == 1);
+                        }
+                    });
+                });
+            }
+
+            return {reset, getByDefine, updateDefaults, applyDefaults};
+        })();
+        
+        var pending_update_calls = 0;   // to keep track of unresolved Promises
 
         function init() {
             refresh_builds();
-            requestBranches(document.getElementById("vehicle").value);
+            onVehicleChange(document.getElementById("vehicle").value);
+        }
+
+        function setMessage (messageText) {
+            document.getElementById("message").innerHTML = messageText;
+        }
+
+        // enables or disables the elements with ids passed as an array
+        // if enable is true, the elements are enabled and vice-versa
+        function enableDisableElementsById(ids, enable) {
+            for (let i=0; i<ids.length; i++) {
+                let element = document.getElementById(ids[i]);
+                if (element) {
+                    element.disabled = (!enable);
+                }
+            }
+        }
+
+        function onVehicleChange(new_vehicle) {
+            // following elemets will be blocked (disabled) when we make the request
+            let elements_to_block = ['vehicle', 'submit', 'reset_def'];
+            enableDisableElementsById(elements_to_block, false);
+            let request_url = '/get_allowed_branches/'+new_vehicle;
+            setMessage("Fetching the list of available branches for "+new_vehicle);
+            pending_update_calls += 1;
+            sendAjaxRequestForJsonResponse(request_url)
+                .then((json_response) => {
+                    let new_branch = json_response.default_branch;
+                    let all_branches = json_response.branches;
+                    updateBranches(all_branches, new_branch);
+                })
+                .catch((message) => {
+                    console.log("Branch update failed. "+message);
+                })
+                .finally(() => {
+                    setMessage("");
+                    enableDisableElementsById(elements_to_block, true);
+                    pending_update_calls -= 1;
+                    fetchAndUpdateDefaults();
+                });
+        }
+
+        function updateBranches(all_branches, new_branch) {
+            let branch_element = document.getElementById('branch');
+            let old_branch = branch_element ? branch_element.value : '';
+            fillBranches(all_branches, new_branch);
+            if (old_branch != new_branch) {
+                onBranchChange(new_branch);
+            }
+        }
+
+        function onBranchChange(new_branch) {
+            // following elemets will be blocked (disabled) when we make the request
+            let elements_to_block = ['branch', 'submit', 'reset_def'];
+            enableDisableElementsById(elements_to_block, false);
+            let request_url = '/boards_and_features/'+new_branch;
+            setMessage("Fetching the list of boards and features for "+new_branch);
+            pending_update_calls += 1;
+            sendAjaxRequestForJsonResponse(request_url)
+                .then((json_response) => {
+                    let boards = json_response.boards;
+                    let new_board = json_response.default_board;
+                    let new_features = json_response.features;
+                    updateBoards(boards, new_board);
+                    Features.reset(new_features);
+                    fillBuildOptions(new_features);
+                })
+                .catch((message) => {
+                    console.log("Boards and features update failed. "+message);
+                })
+                .finally(() => {
+                    setMessage("");
+                    enableDisableElementsById(elements_to_block, true);
+                    pending_update_calls -= 1;
+                    fetchAndUpdateDefaults();
+                });
+        }
+
+        function updateBoards(all_boards, new_board) {
+            let board_element = document.getElementById('board');
+            let old_board = board_element ? board.value : '';
+            fillBoards(all_boards, new_board);
+            if (old_board != new_board) {
+                onBoardChange(new_board);
+            }
+        }
+ 
+        function onBoardChange(new_board) {
+            fetchAndUpdateDefaults();
+        }
+
+        function fetchAndUpdateDefaults() {
+            // return early if there is an unresolved promise (i.e., there is an ongoing ajax call)
+            if (pending_update_calls > 0) {
+                return;
+            }
+            elements_to_block = ['reset_def']
+            enableDisableElementsById(elements_to_block, false);
+            let branch = document.getElementById('branch').value;
+            let vehicle = document.getElementById('vehicle').value;
+            let board = document.getElementById('board').value;
+            let request_url = '/get_defaults/'+vehicle+'/'+branch+'/'+board;
+            setMessage("Fetching defaults for "+vehicle+" and "+board+" on "+branch);
+            sendAjaxRequestForJsonResponse(request_url)
+                .then((json_response) => {
+                    Features.updateDefaults(json_response);
+                    Features.applyDefaults();
+                })
+                .catch((message) => {
+                    console.log("Default reset failed. "+message);
+                })
+                .finally(() => {
+                    setMessage("");
+                    enableDisableElementsById(elements_to_block, true);
+                });
         }
 
         function refresh_builds() {
@@ -87,45 +261,16 @@
             xhr.send();
         }
 
-        function requestBoardsAndFeatures(branch) {
-            var xhr = new XMLHttpRequest();
-            xhr.open('GET', '/boards_and_features/'+branch); // branch consists of both remote and branch_name in format - remote/branch_name. e.g. upstream/master
-            document.getElementById('board_list').innerHTML = "<p>Please wait. Fetching boards on branch "+branch+" ...";
-            document.getElementById('build_options').innerHTML = "<p>Please wait. Fetching build options on branch "+branch+" ...";            
-            document.getElementById("submit").disabled = true;
-            document.getElementById("reset_def").disabled = true;
-            document.getElementById("branch").disabled = true;
-
-            xhr.onload = function () {
-                if (xhr.status == 200) {
-                    response_json = JSON.parse(xhr.response);
-                    boards = response_json['boards'];
-                    default_board = response_json['default_board'];
-                    features = response_json['features'];
-                    fillBoards(boards, default_board);
-                    fillBuildOptions(features);
-                    requestDefaults();
-                    document.getElementById("submit").disabled = false;
-                    document.getElementById("reset_def").disabled = false;
-                } else {
-                    document.getElementById('board_list').innerHTML = "Something went wrong. Please try again. (Response says: "+xhr.response+")";
-                    document.getElementById('build_options').innerHTML = "";      
-                }
-                document.getElementById("branch").disabled = false;
-            }
-            xhr.send();
-        }
-
         function fillBoards(boards, default_board) {
-            var output = document.getElementById('board_list');
+            let output = document.getElementById('board_list');
             output.innerHTML =  "<p>Please select the required options for the custom firmware build, then hit 'Generate'.</p>"+
                                 "<label for='board'>Choose a board: "+
-                                    "<select name='board' id='board'>"+
+                                    "<select name='board' id='board' onchange='onBoardChange(this.value)'>"+
                                     "</select>"+
                                 "</label>";
-            boardList = document.getElementById("board")
+            let boardList = document.getElementById("board")
             boards.forEach(board => {
-                opt = document.createElement('option');
+                let opt = document.createElement('option');
                 opt.value = board;
                 opt.innerHTML = board;
                 opt.selected = (board === default_board);
@@ -134,19 +279,18 @@
         }
 
         function fillBuildOptions(buildOptions) {
-            var output = document.getElementById('build_options');
+            let output = document.getElementById('build_options');
             output.innerHTML =  "<label for='features'>Select Features: "+
                                     "<ul class='collapsibleList' id='outer_list'></ul>"+
                                 "</label>";
-            outerList = document.getElementById("outer_list");
-            define_labels = [];
+            let outerList = document.getElementById("outer_list");
             buildOptions.forEach(category => {
-                outerListItem = document.createElement('li');
+                let outerListItem = document.createElement('li');
                 outerListItem.innerHTML = category['name'];
-                innerList = document.createElement('ul');
+                let innerList = document.createElement('ul');
                 category['options'].forEach(option => {
-                    innerListItem = document.createElement('li');
-                    checkBox = document.createElement('input');
+                    let innerListItem = document.createElement('li');
+                    let checkBox = document.createElement('input');
                     checkBox.type = "checkbox";
                     checkBox.name = option['label'];
                     checkBox.id = option['label'];
@@ -158,7 +302,6 @@
                     innerListItem.appendChild(checkBox);
                     innerListItem.appendChild(document.createTextNode(option['description']));
                     innerList.appendChild(innerListItem);
-                    define_labels[option['define']] = option['label'];
                 });
                 outerListItem.appendChild(innerList);
                 outerList.appendChild(outerListItem);
@@ -172,7 +315,7 @@
                 case f_label:
                     const f_dependency = f_dependency1.split(",")
                     var arrayLength = f_dependency.length;
-                    for (var i = 0; i < arrayLength; i++) {
+                    for (let i = 0; i < arrayLength; i++) {
                         if (document.getElementById(f_dependency[i]).checked == false) {
                             document.getElementById(f_dependency[i]).checked = cb.checked;
                         }
@@ -181,44 +324,30 @@
             }
         }
 
-        function requestBranches(vehicle) {
-            var xhr = new XMLHttpRequest();
-            xhr.open('GET', '/get_allowed_branches/'+vehicle);           
-            document.getElementById("submit").disabled = true;
-            document.getElementById("reset_def").disabled = true;
-            document.getElementById("vehicle").disabled = true;
-
-            xhr.onload = function () {
-                if (xhr.status == 200) {
-                    response = JSON.parse(xhr.response);
-                    branches = response['branches'];
-                    default_branch = response['default_branch'];
-                    old_branch = document.getElementById("branch");
-                    fillBranches(branches);
-                    document.getElementById("submit").disabled = false;
-                    document.getElementById("reset_def").disabled = false;
-                    if (old_branch == null || old_branch.value != default_branch){
-                        // branch has changed
-                        // fetch boards and features again
-                        requestBoardsAndFeatures(default_branch);
+        // returns a Promise
+        // the promise is resolved when we recieve status code 200 from the AJAX request
+        // the JSON response for the request is returned in such case
+        // the promise is rejected when the status code is not 200
+        // the status code is returned in such case
+        function sendAjaxRequestForJsonResponse(url) {
+            return new Promise((resolve, reject) => {
+                var xhr = new XMLHttpRequest();
+                xhr.open('GET', url);           
+                xhr.onload = function () {
+                    if (xhr.status == 200) {
+                        resolve(JSON.parse(xhr.response));
                     } else {
-                        // the new branch and the current branch are same
-                        // branches need not to be changed
-                        // Since the vehicle has changed, reset the defaults
-                        requestDefaults();
+                        reject("Got response:"+xhr.response+" (Status Code: "+xhr.status+")");
                     }
-                } else {
-                    document.getElementById('branch_list').innerHTML = "Something went wrong. Please try again. (Response says: "+xhr.response+")";
                 }
-                document.getElementById("vehicle").disabled = false;
-            }
-            xhr.send();
+                xhr.send();
+            });
         }
 
-        function fillBranches(branches) {
+        function fillBranches(branches, branch_to_select) {
             var output = document.getElementById('branch_list');
             output.innerHTML =  "<label for='branch'>Choose a branch: "+
-                                    "<select name='branch' id='branch' onchange='requestBoardsAndFeatures(this.value);'>"+
+                                    "<select name='branch' id='branch' onchange='onBranchChange(this.value);'>"+
                                     "</select>"+
                                 "</label>";
             branchList = document.getElementById("branch");
@@ -226,42 +355,10 @@
                 opt = document.createElement('option');
                 opt.value = branch['full_name'];
                 opt.innerHTML = branch['label'];
-                opt.selected = (branch['full_name'] === default_branch);
+                opt.selected = (branch['full_name'] === branch_to_select);
                 branchList.appendChild(opt);
             });
         }
-
-        function requestDefaults() {
-            branch = document.getElementById('branch').value;
-            vehicle = document.getElementById('vehicle').value;
-            board = document.getElementById('board').value;
-            document.getElementById("reset_def").disabled = true;
-            
-            var xhr = new XMLHttpRequest();
-            xhr.open('GET', '/get_defaults/'+vehicle+'/'+branch+'/'+board);
-            xhr.onload = function () {
-                if (xhr.status == 200) {
-                    parsed_response = JSON.parse(xhr.response);
-                    setDefaults(parsed_response);
-                } else {
-                    console.log(xhr.response);
-                }
-                document.getElementById("reset_def").disabled = false;
-            }
-            xhr.send();
-        }
-
-        function setDefaults(defines_arr) {
-            for (var i=0; i<defines_arr.length; i++) {
-                var select_opt = (defines_arr[i][0] != '!');
-                var sanitised_define = (select_opt ? defines_arr[i] : defines_arr[i].substring(1)); // this removes the leading '!' from define if it contatins
-                var opt_label = define_labels[sanitised_define];
-                if (opt_label != undefined) {
-                    document.getElementById(opt_label).checked = select_opt;
-                }
-            }
-            return;
-        }
     </script>
 </div>
 </body>