Browse Source

Merge pull request #2359 from Avaiga/1808-allow-custom-javascript-files

Allow custom Javascript files
Nam Nguyen 4 months ago
parent
commit
41532733f8
1 changed files with 45 additions and 13 deletions
  1. 45 13
      taipy/gui/gui.py

+ 45 - 13
taipy/gui/gui.py

@@ -187,6 +187,7 @@ class Gui:
         env_filename: t.Optional[str] = None,
         libraries: t.Optional[t.List[ElementLibrary]] = None,
         flask: t.Optional[Flask] = None,
+        script_paths: t.Optional[t.Union[str, Path, t.List[t.Union[str, Path]]]] = None
     ):
         """Initialize a new Gui instance.
 
@@ -231,12 +232,19 @@ class Gui:
                 If this argument is set, this `Gui` instance will use the value of this argument
                 as the underlying server. If omitted or set to None, this `Gui` will create its
                 own Flask application instance and use it to serve the pages.
+            script_paths (Optional[Union[str, Path, List[Union[str, Path]]]]):
+                Specifies the path(s) to the JavaScript files or external resources used by the application.
+                It can be a single URL or path, or a list containing multiple URLs and/or paths.
         """
         # store suspected local containing frame
         self.__frame = t.cast(FrameType, t.cast(FrameType, currentframe()).f_back)
         self.__default_module_name = _get_module_name_from_frame(self.__frame)
         self._set_css_file(css_file)
 
+        # Set the assets URL path
+        self.__script_files: list[str] = []
+        self.__load_scripts(script_paths)
+
         # Preserve server config for server initialization
         if path_mapping is None:
             path_mapping = {}
@@ -411,6 +419,30 @@ class Gui:
             for library in libraries:
                 Gui.add_library(library)
 
+    def __load_scripts(self, script_paths: t.Optional[t.Union[str, Path, t.List[t.Union[str, Path]]]]):
+        if script_paths is None:
+            return
+        else:
+            if isinstance(script_paths, (str, Path)):
+                script_paths = [script_paths]
+
+            for script_path in script_paths:
+                if isinstance(script_path, str):
+                    parsed_url = urlparse(script_path)
+                    if parsed_url.netloc:
+                        self.__script_files.append(script_path)
+                        continue
+                    script_path = Path(script_path)
+
+                if isinstance(script_path,
+                              Path) and script_path.exists() and script_path.is_file() and script_path.suffix == ".js":
+                    self.__script_files.append(str(script_path))
+                else:
+                    _warn(f"Script path '{script_path}' does not exist, is not a file, or is not a JavaScript file.")
+
+            if not self.__script_files:
+                _warn("No JavaScript files found in the specified script paths.")
+
     @staticmethod
     def add_library(library: ElementLibrary) -> None:
         """Add a custom visual element library.
@@ -474,7 +506,6 @@ class Gui:
                     from plotly.graph_objs import Figure as PlotlyFigure  # type: ignore[reportMissingImports]
 
                     if isinstance(content, PlotlyFigure):
-
                         def get_plotly_content(figure: PlotlyFigure):
                             return figure.to_html()
 
@@ -486,7 +517,6 @@ class Gui:
                     from matplotlib.figure import Figure as MatplotlibFigure
 
                     if isinstance(content, MatplotlibFigure):
-
                         def get_matplotlib_content(figure: MatplotlibFigure):
                             import base64
                             from io import BytesIO
@@ -800,12 +830,12 @@ class Gui:
         if var_name.startswith(_TaipyBase._HOLDER_PREFIX):
             for hp in _TaipyBase._get_holder_prefixes():
                 if var_name.startswith(hp):
-                    var_name = var_name[len(hp) :]
+                    var_name = var_name[len(hp):]
                     break
         suffix_var_name = ""
         if "." in var_name:
             first_dot_index = var_name.index(".")
-            suffix_var_name = var_name[first_dot_index + 1 :]
+            suffix_var_name = var_name[first_dot_index + 1:]
             var_name = var_name[:first_dot_index]
         var_name_decode, module_name = _variable_decode(self._get_expr_from_hash(var_name))
         current_context = self._get_locals_context()
@@ -1202,8 +1232,7 @@ class Gui:
                             if ret_payload:
                                 break
                         except Exception as e:  # pragma: no cover
-                            _warn(
-                                f"Exception raised in '{lib_name}.get_data({lib_name}, payload, {user_var_name}, value)'",  # noqa: E501
+                            _warn(f"Exception raised in '{lib_name}.get_data({lib_name}, payload, {user_var_name}, value)'", # noqa: E501
                                 e,
                             )
             if not isinstance(ret_payload, dict):
@@ -1277,9 +1306,9 @@ class Gui:
             k: v
             for k, v in vars(self._get_data_scope()).items()
             if not k.startswith("_")
-            and not callable(v)
-            and "TpExPr" not in k
-            and not isinstance(v, (ModuleType, FunctionType, LambdaType, type, Page))
+               and not callable(v)
+               and "TpExPr" not in k
+               and not isinstance(v, (ModuleType, FunctionType, LambdaType, type, Page))
         }
         function_data = {
             k: v
@@ -2026,7 +2055,7 @@ class Gui:
             raise Exception("name is required for add_page() function.")
         if not Gui.__RE_PAGE_NAME.match(name):  # pragma: no cover
             raise SyntaxError(
-                f'Page name "{name}" is invalid. It must only contain letters, digits, dash (-), underscore (_), and forward slash (/) characters.'  # noqa: E501
+                f'Page name "{name}" is invalid. It must only contain letters, digits, dash (-), underscore (_), and forward slash (/) characters.' # noqa: E501
             )
         if name.startswith("/"):  # pragma: no cover
             raise SyntaxError(f'Page name "{name}" cannot start with forward slash (/) character.')
@@ -2224,7 +2253,7 @@ class Gui:
                 self._bind(encoded_var_name, bind_locals[var_name])
             else:
                 _warn(
-                    f"Variable '{var_name}' is not available in either the '{self._get_locals_context()}' or '__main__' modules."  # noqa: E501
+                    f"Variable '{var_name}' is not available in either the '{self._get_locals_context()}' or '__main__' modules." # noqa: E501
                 )
         return encoded_var_name
 
@@ -2593,7 +2622,7 @@ class Gui:
                 _warn(f"Using webapp_path: '{_conf_webapp_path}'.")
             else:  # pragma: no cover
                 _warn(
-                    f"webapp_path: '{_conf_webapp_path}' is not a valid directory. Falling back to '{_webapp_path}'."  # noqa: E501
+                    f"webapp_path: '{_conf_webapp_path}' is not a valid directory. Falling back to '{_webapp_path}'." # noqa: E501
                 )
         return _webapp_path
 
@@ -2730,6 +2759,9 @@ class Gui:
         if self.__css_file:
             styles.append(f"{self.__css_file}")
 
+        if self.__script_files:
+            scripts.extend(self.__script_files)
+
         self._flask_blueprint.append(extension_bp)
 
         _webapp_path = self._get_webapp_path()
@@ -2905,7 +2937,7 @@ class Gui:
                             glob_ctx[lib_context[0]] = lib_context[1]
                     elif lib_context:
                         _warn(
-                            f"Method {name}.on_init() should return a Tuple[str, Any] where the first element must be a valid Python identifier."  # noqa: E501
+                            f"Method {name}.on_init() should return a Tuple[str, Any] where the first element must be a valid Python identifier." # noqa: E501
                         )
                 except Exception as e:  # pragma: no cover
                     if not self._call_on_exception(f"{name}.on_init", e):