This is an automated email from the ASF dual-hosted git repository.

bbovenzi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 6a5b22df0e Replaced bootstrap-typeahead with jquery-ui (#40744)
6a5b22df0e is described below

commit 6a5b22df0e2f399afc8e952165fd005b81101e08
Author: SAI GANESH S <[email protected]>
AuthorDate: Tue Nov 5 22:19:36 2024 +0530

    Replaced bootstrap-typeahead with jquery-ui (#40744)
    
    * Replaced Bootstrap-typeahead with jquery-ui for typeahead functionality 
for searching other timezones, present at the navbar.
    
    * Replace bootstrap-typeahead with jquery-ui in dags.js
    
    * Removed bootstrap3-typeahead package
    
    * Added custom css for typeahead
    
    * Fixed linting issues in dags.js
    
    * fixed linting issue in main.css
---
 airflow/www/package.json                |  2 +-
 airflow/www/static/css/main.css         | 18 +++++++
 airflow/www/static/js/dags.js           | 79 +++++++++++++++++++---------
 airflow/www/static/js/main.js           | 91 +++++++++++++++++++++++----------
 airflow/www/templates/airflow/main.html |  4 +-
 airflow/www/webpack.config.js           |  9 +++-
 airflow/www/yarn.lock                   | 19 ++++---
 7 files changed, 159 insertions(+), 63 deletions(-)

diff --git a/airflow/www/package.json b/airflow/www/package.json
index 79991b3bb9..638e782139 100644
--- a/airflow/www/package.json
+++ b/airflow/www/package.json
@@ -109,7 +109,6 @@
     "@visx/shape": "^2.12.2",
     "ansi_up": "^6.0.2",
     "axios": "^1.7.4",
-    "bootstrap-3-typeahead": "^4.0.2",
     "camelcase-keys": "^7.0.0",
     "chakra-react-select": "^4.0.0",
     "codemirror": "^5.59.1",
@@ -124,6 +123,7 @@
     "eonasdan-bootstrap-datetimepicker": "^4.17.47",
     "framer-motion": "^6.0.0",
     "jquery": ">=3.5.0",
+    "jquery-ui": "1.13.3",
     "jshint": "^2.13.4",
     "json-to-pretty-yaml": "^1.2.2",
     "lodash": "^4.17.21",
diff --git a/airflow/www/static/css/main.css b/airflow/www/static/css/main.css
index 0a2810c52e..9d9ed1e9fc 100644
--- a/airflow/www/static/css/main.css
+++ b/airflow/www/static/css/main.css
@@ -288,10 +288,28 @@ label[for="timezone-other"],
   font-weight: normal;
 }
 
+#timezone-menu {
+  overflow-x: hidden;
+}
+
 #timezone-menu ul.typeahead.dropdown-menu {
   max-height: 200px;
   overflow-y: auto;
   overflow-x: hidden;
+  border: none;
+  left: 0 !important;
+}
+
+#timezone-menu .dropdown-item {
+  padding: 3px 20px;
+}
+
+.ui-menu .ui-menu-item a.ui-state-focus,
+.ui-menu .ui-menu-item a.ui-state-active {
+  border: 1px solid white;
+  border-left: 1px solid #ccc;
+  background: #f6f6f6;
+  color: #454545;
 }
 
 /* depending on the version of FAB in use, we may have a style conflict */
diff --git a/airflow/www/static/js/dags.js b/airflow/www/static/js/dags.js
index 9228ca630b..66ea124487 100644
--- a/airflow/www/static/js/dags.js
+++ b/airflow/www/static/js/dags.js
@@ -116,31 +116,60 @@ $.each($("[id^=toggle]"), function toggleId() {
   });
 });
 
-$(".typeahead").typeahead({
-  source(query, callback) {
-    return $.ajax(autocompleteUrl, {
-      data: {
-        query: encodeURIComponent(query),
-        status: statusFilter,
-      },
-      success: callback,
-    });
-  },
-  displayText(value) {
-    return value.dag_display_name || value.name;
-  },
-  autoSelect: false,
-  afterSelect(value) {
-    const query = new URLSearchParams(window.location.search);
-    query.set("search", value.name);
-    if (value.type === "owner") {
-      window.location = `${DAGS_INDEX}?${query}`;
-    }
-    if (value.type === "dag") {
-      window.location = `${gridUrl.replace("__DAG_ID__", 
value.name)}?${query}`;
-    }
-  },
-});
+// eslint-disable-next-line no-underscore-dangle
+$(".typeahead")
+  .autocomplete({
+    autoFocus: true,
+    source: (request, response) => {
+      $.ajax({
+        url: autocompleteUrl,
+        data: {
+          query: encodeURIComponent(request.term),
+          status: statusFilter,
+        },
+        success: (data) => {
+          response(data);
+        },
+      });
+    },
+    focus: (event) => {
+      // Prevents value from being inserted on focus
+      event.preventDefault();
+    },
+    select: (_, ui) => {
+      const value = ui.item;
+      const query = new URLSearchParams(window.location.search);
+      query.set("search", value.name);
+      if (value.type === "owner") {
+        window.location = `${DAGS_INDEX}?${query}`;
+      }
+      if (value.type === "dag") {
+        window.location = `${gridUrl.replace(
+          "__DAG_ID__",
+          value.name
+        )}?${query}`;
+      }
+    },
+    appendTo: "#search_form > div",
+  })
+  .data("ui-autocomplete")._renderMenu = function (ul, items) {
+  ul.addClass("typeahead dropdown-menu");
+  $.each(items, function (_, item) {
+    // eslint-disable-next-line no-underscore-dangle
+    this._renderItemData(ul, item);
+  });
+};
+
+// eslint-disable-next-line no-underscore-dangle
+$.ui.autocomplete.prototype._renderItem = function (ul, item) {
+  return $("<li>")
+    .append(
+      $("<a>")
+        .addClass("dropdown-item")
+        .text(item.dag_display_name || item.name)
+    )
+    .appendTo(ul);
+};
 
 $("#search_form").on("reset", () => {
   const query = new URLSearchParams(window.location.search);
diff --git a/airflow/www/static/js/main.js b/airflow/www/static/js/main.js
index 95861ea6c1..c60827c7a3 100644
--- a/airflow/www/static/js/main.js
+++ b/airflow/www/static/js/main.js
@@ -180,35 +180,70 @@ function initializeUITimezone() {
   $("a[data-timezone]").click((evt) => {
     changeDisplayedTimezone($(evt.currentTarget).data("timezone"));
   });
-
-  $("#timezone-other").typeahead({
-    source: $(
-      moment.tz.names().map((tzName) => {
-        const category = tzName.split("/", 1)[0];
-        return { category, name: tzName.replace("_", " "), tzName };
-      })
-    ),
-    showHintOnFocus: "all",
-    showCategoryHeader: true,
-    items: "all",
-    afterSelect(data) {
-      // Clear it for next time we open the pop-up
-      this.$element.val("");
-
-      setManualTimezone(data.tzName);
-      changeDisplayedTimezone(data.tzName);
-
-      // We need to delay the close event to not be in the form handler,
-      // otherwise bootstrap ignores it, thinking it's caused by interaction on
-      // the <form>
-      setTimeout(() => {
-        document.activeElement.blur();
-        // Bug in typeahed, it thinks it's still shown!
-        this.shown = false;
-        this.focused = false;
-      }, 1);
-    },
+  // Prepare the data source
+  const timezoneData = moment.tz.names().map((tzName) => {
+    const category = tzName.split("/", 1)[0];
+    return { category, label: tzName.replace("_", " "), value: tzName };
   });
+
+  // Create a custom filter function to include categories
+  function filterByCategory(array, term) {
+    const matcher = new RegExp($.ui.autocomplete.escapeRegex(term), "i");
+    return $.grep(
+      array,
+      (item) => matcher.test(item.label) || matcher.test(item.category)
+    );
+  }
+
+  // Initialize jQuery UI Autocomplete
+  // eslint-disable-next-line no-underscore-dangle
+  $("#timezone-other")
+    .autocomplete({
+      source: (request, response) => {
+        const results = filterByCategory(timezoneData, request.term);
+        response(results);
+      },
+      appendTo: "#timezone-menu > li:nth-child(6) > form",
+      focus: (event, ui) => {
+        // Prevent the value from being inserted on focus
+        event.preventDefault();
+        $(this).val(ui.item.label);
+      },
+      select: (event, ui) => {
+        // Clear it for next time we open the pop-up
+        $(this).val("");
+
+        setManualTimezone(ui.item.value);
+        changeDisplayedTimezone(ui.item.value);
+
+        return false;
+      },
+    })
+    .data("ui-autocomplete")._renderItem = function (ul, item) {
+    const $li = $("<li>");
+    $li.append(
+      `<a class='dropdown-item' href='#' role='option'>${item.label}</a>`
+    );
+    return $li.appendTo(ul);
+  };
+
+  // Custom rendering function to include category headers
+  // eslint-disable-next-line no-underscore-dangle
+  $.ui.autocomplete.prototype._renderMenu = function (ul, items) {
+    let currentCategory = "";
+    ul.addClass("typeahead dropdown-menu");
+    ul.attr("role", "listbox");
+    $.each(items, (index, item) => {
+      if (item.category !== currentCategory) {
+        ul.append(
+          `<li class='ui-autocomplete-category 
dropdown-header'>${item.category}</li>`
+        );
+        currentCategory = item.category;
+      }
+      // eslint-disable-next-line no-underscore-dangle
+      this._renderItemData(ul, item);
+    });
+  };
 }
 
 function filterOpSelected(ele) {
diff --git a/airflow/www/templates/airflow/main.html 
b/airflow/www/templates/airflow/main.html
index 008418d7e5..3842d3602d 100644
--- a/airflow/www/templates/airflow/main.html
+++ b/airflow/www/templates/airflow/main.html
@@ -46,6 +46,7 @@
   <link rel="stylesheet" type="text/css" href="{{ url_for_asset('main.css') 
}}">
   <link rel="stylesheet" type="text/css" href="{{ 
url_for_asset('loadingDots.css') }}">
   <link rel="stylesheet" type="text/css" href="{{ 
url_for_asset('bootstrap-datetimepicker.min.css') }}">
+  <link rel="stylesheet" type="text/css" href="{{ 
url_for_asset('jquery-ui.min.css') }}">
   <style type="text/css">
     {% for state, state_color in state_color_mapping.items() %}
       span.{{state}} {
@@ -133,9 +134,10 @@
     $('time[title]').tooltip();
   </script>
   <script src="{{ url_for_asset('moment.js') }}"></script>
+  <script src="{{ url_for_asset('jquery-ui.min.js') }}"></script>
   <script src="{{ url_for_asset('main.js') }}"></script>
   <script src="{{ url_for_asset('bootstrap-datetimepicker.min.js') 
}}"></script>
-  <script src="{{ url_for_asset('bootstrap3-typeahead.min.js') }}"></script>
+
   <script src="{{ url_for_asset('toggleTheme.js') }}"></script>
 
   {% if analytics_tool is defined and analytics_tool %}
diff --git a/airflow/www/webpack.config.js b/airflow/www/webpack.config.js
index 633d9a6810..9d5800f783 100644
--- a/airflow/www/webpack.config.js
+++ b/airflow/www/webpack.config.js
@@ -211,12 +211,17 @@ const config = {
           to: "d3-tip.js",
           flatten: true,
         },
+
         {
-          from: "node_modules/bootstrap-3-typeahead/*min.*",
+          from: 
"node_modules/eonasdan-bootstrap-datetimepicker/build/css/bootstrap-datetimepicker.min.css",
           flatten: true,
         },
         {
-          from: 
"node_modules/eonasdan-bootstrap-datetimepicker/build/css/bootstrap-datetimepicker.min.css",
+          from: "node_modules/jquery-ui/dist/jquery-ui.min.js",
+          flatten: true,
+        },
+        {
+          from: "node_modules/jquery-ui/dist/themes/base/jquery-ui.min.css",
           flatten: true,
         },
         {
diff --git a/airflow/www/yarn.lock b/airflow/www/yarn.lock
index c8b9aaef5b..d1a8d93cdf 100644
--- a/airflow/www/yarn.lock
+++ b/airflow/www/yarn.lock
@@ -4441,11 +4441,6 @@ boolbase@^1.0.0:
   resolved 
"https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e";
   integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
 
-bootstrap-3-typeahead@^4.0.2:
-  version "4.0.2"
-  resolved 
"https://registry.yarnpkg.com/bootstrap-3-typeahead/-/bootstrap-3-typeahead-4.0.2.tgz#cb1c969044856862096fc8c71cc21b3acbb50412";
-  integrity sha1-yxyWkESFaGIJb8jHHMIbOsu1BBI=
-
 bootstrap@^3.3:
   version "3.4.1"
   resolved 
"https://registry.yarnpkg.com/bootstrap/-/bootstrap-3.4.1.tgz#c3a347d419e289ad11f4033e3c4132b87c081d72";
@@ -7991,11 +7986,23 @@ jest@^27.3.1:
     import-local "^3.0.2"
     jest-cli "^27.3.1"
 
-jquery@>=3.5.0, jquery@^3.5.1:
[email protected]:
+  version "1.13.3"
+  resolved 
"https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.13.3.tgz#d9f5292b2857fa1f2fdbbe8f2e66081664eb9bc5";
+  integrity 
sha512-D2YJfswSJRh/B8M/zCowDpNFfwsDmtfnMPwjJTyvl+CBqzpYwQ+gFYIbUUlzijy/Qvoy30H1YhoSui4MNYpRwA==
+  dependencies:
+    jquery ">=1.8.0 <4.0.0"
+
+jquery@>=1.7, jquery@>=3.5.0, jquery@^3.5.1:
   version "3.6.0"
   resolved 
"https://registry.yarnpkg.com/jquery/-/jquery-3.6.0.tgz#c72a09f15c1bdce142f49dbf1170bdf8adac2470";
   integrity 
sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==
 
+"jquery@>=1.8.0 <4.0.0":
+  version "3.7.1"
+  resolved 
"https://registry.yarnpkg.com/jquery/-/jquery-3.7.1.tgz#083ef98927c9a6a74d05a6af02806566d16274de";
+  integrity 
sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==
+
 js-levenshtein@^1.1.6:
   version "1.1.6"
   resolved 
"https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d";

Reply via email to