Избавиться от жесткого кодирования контекстного пути веб-приложений во внешних файлах JavaScript

У меня есть несколько конечных точек WebSockets, таких как

wss://localhost:8181/ContextPath/Push

Все такие конечные URL-адреса жестко закодированы в отдельных внешних файлах JavaScript (.js). Эти файлы JavaScript включаются в соответствующие файлы XHTML по мере необходимости. Имя хоста и путь к контексту должны оцениваться программно, а не жестко кодироваться во всем, где они требуются.

Имя хоста (localhost:8181) можно получить в JavaScript с помощью document.location.host, но в JavaScript нет стандартного/канонического способа для получения пути к контексту, в котором выполняется приложение.


Я делаю что-то вроде следующего.

Глобальная переменная JavaScript объявляется на основном шаблоне следующим образом.

<f:view locale="#{bean.locale}" encoding="UTF-8" contentType="text/html">
        <f:loadBundle basename="messages.ResourceBundle" var="messages"/>

        <ui:param name="contextPath" value="#{request.contextPath}"/>
        <ui:insert name="metaData"></ui:insert>

        <h:head>
            <script type="text/javascript">var contextPath = "#{contextPath}";</script>
        </h:head>

        <h:body id="body">

        </h:body>
    </f:view>
</html>

Файлы JavaScript, в которых имя хоста и путь контекста жестко закодированы, включены в соответствующие шаблонные клиенты или любые разделы шаблона на север, юг, восток и запад следующим образом.

<html lang="#{bean.language}"
      xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html">

    <h:form>
        <h:outputScript library="default" name="js/websockets.js" target="head"/>
    </h:form>

Только для точки зрения websockets.js выглядит следующим образом (вы можете просто игнорировать его).

if (window.WebSocket) {
    // The global variable "contextPath" is unavailable here
    // because it is declared afterwards in the generated HTML.
    var ws = new WebSocket("wss://"+document.location.host + contextPath + "/Push");
    ws.onmessage = function (event) {
        // This handler is invoked, when a message is received through a WebSockets channel.
    };

    $(window).on('beforeunload', function () {
        ws.close();
    });
} else {}

Теперь ожидается, что глобальная переменная JavaScript contextPath, объявленная в главном шаблоне, будет доступна во включенном файле JavaScript, а именно websockets.js. Это, однако, неверно.

Что происходит, так это то, что к файлу JavaScript с включенным кодом websockets.js, к которому была обращена глобальная переменная contextPath, помещается до жестко закодированный тег <script> в сгенерированном HTML <head> в основном шаблоне.

Другими словами, глобальная переменная JavaScript contextPath фактически пыталась использовать во включенном файле websockets.js перед объявлением.

В любом случае, как избавиться от жесткого кодирования пути контекста во внешних файлах JavaScript?

Единственная цель этого - в отличие от файлов CSS, EL не оценивается во внешних файлах JavaScript. Поэтому #{} вещь не будет работать, если она не будет помещена в файл XHTML.

Ответ 1

Что произойдет, так это то, что к файлу JavaScript с именем websockets.js, к которому была обращена глобальная переменная contextPath, помещается до тэг <script> с жестким кодом в сгенерированном HTML тег <head> в главном шаблоне

Это неожиданно. Вы объявили <h:outputScript> ссылающийся websockets.js файл внутри <h:body> с помощью target="head". Предполагается, что это будет после всех других script ресурсов, уже объявленных в <h:head>. См. Также a.o. Как ссылаться на ресурс CSS/JS/image в шаблоне Facelets? В конце концов, это связано с тем, что PrimeFaces поставляется в комплекте HeadRenderer, который намерен автоматически включать некоторые ресурсы CSS и заботиться о <facet name="first|middle|last">.

Это стоит отчет о проблемах для парней PF (если это еще не сделано). В то же время лучше всего отключить его, явно зарегистрировав собственную реализацию JSF HeadRenderer как показано ниже в faces-config.xml (при условии, что вы используете Mojarra).

<render-kit>
    <renderer>
        <component-family>javax.faces.Output</component-family>
        <renderer-type>javax.faces.Head</renderer-type>
        <renderer-class>com.sun.faces.renderkit.html_basic.HeadRenderer</renderer-class>
    </renderer>
</render-kit>

И в явном виде включите тематику theme.css для темы PrimeFaces, как показано ниже в <h:head>:

<h:outputStylesheet library="primefaces-aristo" name="theme.css" />

Возвращаясь к реальному вопросу,

В любом случае, как избавиться от жесткого кодирования пути контекста во внешних файлах JavaScript?

Либо установите его как базовый URI (обратите внимание: относительный путь не поддерживается в HTML4/IE6-8).

<h:head>
    <base href="#{request.contextPath}/" />
    ...
</h:head>
var baseURI = $("base").attr("href");

Или установите его как атрибут данных корневого элемента HTML.

<!DOCTYPE html>
<html lang="en" data-baseuri="#{request.contextPath}/" ...>
    ...
</html>
var baseURI = $("html").data("baseuri");

Не привязанный к конкретной проблеме, как совет, чтобы прозрачно охватывать как http + ws, так и https + wss, подумайте об использовании location.protocol вместо жестко закодированного wss.

var ws = new WebSocket(location.protocol.replace("http", "ws") + "//" + location.host + baseURI + "Push");

Ответ 2

Является ли следующая опция для вас?

  • Определите скрытый html-тег в главном шаблоне, например:

    <span id="pageContextPath" data="#{contextPath}" style="display:none;"></span>
    
  • Измените код JavaScript на что-то вроде:

    jQuery(document).ready(function ($) {
        if (window.WebSocket) {
            contextPath = $("#pageContextPath").attr("data");
            var ws = new WebSocket("wss://" + document.location.host + contextPath + "/Push");
            //...
        } else {}
    });
    

Я использовал здесь jQuery. Вы можете переписать его на простом JavaScript. Но это должно быть сделано после того, как "документ готов", чтобы убедиться, что скрытый тег был визуализирован. иначе js не найдет этот элемент.