You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by ry...@apache.org on 2021/02/11 13:59:35 UTC

[airflow] branch master updated: Rework client-side script for connection form. (#14052)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 98bbe5a  Rework client-side script for connection form. (#14052)
98bbe5a is described below

commit 98bbe5aec578a012c1544667bf727688da1dadd4
Author: Wong Yong Jie <yj...@gmail.com>
AuthorDate: Thu Feb 11 21:59:21 2021 +0800

    Rework client-side script for connection form. (#14052)
    
    * Rework client-side script for connection form.
    
    Before this, fields on the connection form were dynamically changed by
    adding and removing the "hide" CSS class. If a provider were to add a
    field that requires validation (e.g. InputRequired), or if a different
    field type was used (e.g. NumberField), the entire form would be
    unsaveable, even if the currently selected connection type had nothing
    to do with that provider.
    
    This change takes a different approach; upon loading, the DOM elements
    for all extra fields are saved into a Map, then removed from the DOM
    tree. When another connection type is selected, these elements are
    restored into the DOM.
    
    * Use template string literals in connection form.
    
    Co-authored-by: Ryan Hamilton <ry...@ryanahamilton.com>
    
    Co-authored-by: Ryan Hamilton <ry...@ryanahamilton.com>
---
 airflow/www/static/js/connection_form.js | 138 +++++++++++++++++++++++++------
 1 file changed, 111 insertions(+), 27 deletions(-)

diff --git a/airflow/www/static/js/connection_form.js b/airflow/www/static/js/connection_form.js
index af6601b..89ecde0 100644
--- a/airflow/www/static/js/connection_form.js
+++ b/airflow/www/static/js/connection_form.js
@@ -24,40 +24,124 @@ function decode(str) {
   return new DOMParser().parseFromString(str, "text/html").documentElement.textContent
 }
 
+/**
+ * Returns a map of connection type to its controls.
+ */
+function getConnTypesToControlsMap() {
+  const connTypesToControlsMap = new Map();
+
+  const extraFormControls = Array.from(document.querySelectorAll("[id^='extra__'"));
+  extraFormControls.forEach(control => {
+    const connTypeEnd = control.id.indexOf('__', 'extra__'.length);
+    const connType = control.id.substring('extra__'.length, connTypeEnd);
+
+    const controls = connTypesToControlsMap.has(connType)
+      ? connTypesToControlsMap.get(connType)
+      : [];
+
+    controls.push(control.parentElement.parentElement);
+    connTypesToControlsMap.set(connType, controls);
+  });
+
+  return connTypesToControlsMap;
+}
+
+/**
+ * Returns the DOM element that contains the different controls.
+ */
+function getControlsContainer() {
+  return document.getElementById('conn_id')
+    .parentElement
+    .parentElement
+    .parentElement;
+}
+
 $(document).ready(function () {
+  const fieldBehavioursElem = document.getElementById('field_behaviours');
+  const config = JSON.parse(decode(fieldBehavioursElem.textContent));
 
-  function connTypeChange(connectionType) {
-    $(".hide").removeClass("hide");
-    $.each($("[id^='extra__']"), function () {
-      $(this).parent().parent().addClass('hide')
+  // Save all DOM elements into a map on load.
+  const controlsContainer = getControlsContainer();
+  const connTypesToControlsMap = getConnTypesToControlsMap();
+
+  /**
+   * Changes the connection type.
+   * @param {string} connType The connection type to change to.
+   */
+  function changeConnType(connType) {
+    Array.from(connTypesToControlsMap.values()).forEach(controls => {
+      controls
+        .filter(control => control.parentElement === controlsContainer)
+        .forEach(control => controlsContainer.removeChild(control));
     });
-    $.each($("[id^='extra__" + connectionType + "__']"), function () {
-      $(this).parent().parent().removeClass('hide')
+
+    const controls = connTypesToControlsMap.get(connType) || [];
+    controls.forEach(control => controlsContainer.appendChild(control));
+
+    // Restore field behaviours.
+    restoreFieldBehaviours();
+
+    // Apply behaviours to fields.
+    applyFieldBehaviours(connType);
+  }
+
+  /**
+   * Restores the behaviour for all fields. Used to restore fields to a
+   * well-known state during the change of connection types.
+   */
+  function restoreFieldBehaviours() {
+    Array.from(document.querySelectorAll('label[data-origText]')).forEach(elem => {
+      elem.innerText = elem.dataset.origText;
+      delete elem.dataset.origText;
     });
-    $("label[orig_text]").each(function () {
-      $(this).text($(this).attr("orig_text"));
+
+    Array.from(document.querySelectorAll('.form-control')).forEach(elem => {
+      elem.placeholder = '';
+      elem.parentElement.parentElement.classList.remove('hide');
     });
-    $(".form-control").each(function(){$(this).attr('placeholder', '')});
-    let config = JSON.parse(decode($("#field_behaviours").text()))
-    if (config[connectionType] != undefined) {
-      $.each(config[connectionType].hidden_fields, function (i, field) {
-        $("#" + field).parent().parent().addClass('hide')
-      });
-      $.each(config[connectionType].relabeling, function (k, v) {
-        lbl = $("label[for='" + k + "']");
-        lbl.attr("orig_text", lbl.text());
-        $("label[for='" + k + "']").text(v);
-      });
-      $.each(config[connectionType].placeholders, function(k, v){
-        $("#" + k).attr('placeholder', v);
-      });
+  }
+
+  /**
+   * Applies special behaviour for fields. The behaviour is defined through
+   * config, passed by the server.
+   *
+   * @param {string} connType The connection type to apply.
+   */
+  function applyFieldBehaviours(connType) {
+    if (config[connType]) {
+      if (Array.isArray(config[connType].hidden_fields)) {
+        config[connType].hidden_fields.forEach(field => {
+          document.getElementById(field)
+            .parentElement
+            .parentElement
+            .classList
+            .add('hide');
+        });
+      }
+
+      if (config[connType].relabeling) {
+        Object.keys(config[connType].relabeling).forEach(field => {
+          const label = document.querySelector(`label[for='${field}']`);
+          label.dataset.origText = label.innerText;
+          label.innerText = config[connType].relabeling[field];
+        });
+      }
+
+      if (config[connType].placeholders) {
+        Object.keys(config[connType].placeholders).forEach(field => {
+          const placeholder = config[connType].placeholders[field];
+          document.getElementById(field).placeholder = placeholder;
+        });
+      }
     }
   }
 
-  var connectionType = $("#conn_type").val();
-  $("#conn_type").on('change', function (e) {
-    connectionType = $("#conn_type").val();
-    connTypeChange(connectionType);
+  const connTypeElem = document.getElementById('conn_type');
+  $(connTypeElem).on('change', (e) => {
+    connType = e.target.value;
+    changeConnType(connType);
   });
-  connTypeChange(connectionType);
+
+  // Initialize the form by setting a connection type.
+  changeConnType(connTypeElem.value);
 });