Статьи

Прозрачная передача данных из серверного скрипта в клиентский JavaScript

При разработке веб-приложения иногда приходилось передавать информацию с сервера компоненту JavaScript в момент создания страницы. Я считаю, что общим решением является использование элемента <script> на странице HTML, который инициализирует компонент данными и генерируется серверным скриптом (PHP, Groovy, Python и т. Д.). Тем не менее, при написании повторно используемых компонентов JavaScript это не поддерживаемое решение.

Проблема заключается в том, что компонент может использоваться несколько раз (или даже вообще не использоваться), но каждый раз, когда компонент включается в страницу HTML, выполняется один и тот же сценарий инициализации. Это сработало бы, если бы каждый компонент был идентифицирован уникальным идентификатором, однако это не является хорошей практикой.

Я всегда стараюсь улучшить то, что кажется громоздким, поэтому в своих проектах я использую декларативный подход в теле HTML. Этот подход отменяет ответственность за инициализацию компонентов JavaScript с сгенерированной страницы во внешний файл js. Поэтому единственный элемент <script>, размещенный на странице HTML, — это ссылка на внешний файл js, который вызывает функцию инициализатора после загрузки документа.

Окончательное решение может выглядеть так:

<div class="my-javascript-button">
    <div class="config" style="display: none;">
        <div class="title">Go to www.google.com</div>
        <a class="url" href="http://www.google.com"/>
    </div>
</div>

или же

<div class="my-javascript-button">
    <div class="config" style="display:none;">
        <!-- JSON object: -->
        { "title" : "Go to www.google.com",
          "url" : "http://www.google.com"
        }
     </div>
</div>

Чтобы прочитать о лучшем способе его кодирования, перейдите непосредственно к решению «Золотое сечение».

Описание проблемы:

  • Данные в скрипте сервера, который генерирует страницу динамически (PHP, Ruby, Python, …)
  • HTML-страница, сгенерированная скриптом
  • Javascript включен в HTML-страницу (JavaScript является статическим, а не генерируется)

Поэтому конфигурация из серверного скрипта может быть передана на HTML-страницу (отображаемую на странице), но не в статические JavaScript-скрипты.

Мы хотим создать инкапсулированные компоненты HTML + JavaScript, которые могут быть вставлены один или несколько раз в сгенерированную HTML-страницу и могут быть переданы в качестве объекта конфигурации из сценария на стороне сервера для настройки их поведения.

Простое решение с использованием JSON (все еще громоздко) 

 

Требования:

  • требует поддержки JSON и случайной генерации в скрипте сервера
  • требуется фрагмент кода JavaScript на странице HTML

Оба требования делают решение немного обременительным, но понимание этого решения поможет вам понять идею.

Описание:

  1. Для каждого фрагмента HTML-кода (который помещает компонент в сгенерированную страницу), сгенерируйте случайный текстовый идентификатор

    • def randomId = getRandomId('my-javascript-button-')
  2. Пометьте этот фрагмент HTML с помощью randomId, либо по идентификатору атрибута, либо как класс css, либо как другой атрибут:

    • <div class="my-javascript-button ${randomId}">
          ... rest of html goes here
      </div>
  3. Во внешнем файле JavaScript для компонента создайте функцию инициализатора для этого типа конфигурации, которая принимает два параметра: randomId для идентификации целевого элемента html и объект, содержащий конфигурацию:

    • function initializeMyJavascriptButton(id, config) {
      }
  4. Вызовите функцию инициализатора со страницы, передавая randomId и объект сценария, закодированный в JSON:

    • Пример в Grails GSP: 
      initializeMyJavascriptButton("${randomId}" ,  
                                 ${config as JSON} ); 

Полученные результаты:

  • Код JavaScript, который может быть эффективно передан конфигурации
  • Элемент HTML и его конфигурация визуально разделены в исходном коде HTML как блоки HTML и JavaScript — они связаны сгенерированным случайным идентификатором

Декларативное решение с использованием невидимой структуры DOM

Простое решение JSON работает, но его нелегко обслуживать и читать. Сгенерированные идентификаторы — нам это действительно нужно? Разве мы не можем просто пометить все компоненты общим классом? Где находится конфигурация компонента в другом блоке HTML? Почему бы не поместить его непосредственно в место, где компонент объявлен, как его атрибуты, или, по крайней мере, во вложенный блок? Кроме того, генерация кода JavaScript не является идеальной идеей, есть ли способ поместить весь код в отдельный файл или даже в библиотеку?

В конце концов, основной вопрос: зачем нам нужен JavaScript, чтобы просто объявить компонент вместе с его атрибутами, когда HTML достаточно богат, чтобы сделать это?

Представьте себе эту декларацию:

<div class="my-javascript-button" title="Go to www.google.com" 
      url="http://www.google.com">
</div>

Видишь, о чем я?

Хотя приведенный выше код не является допустимым кодом XHTML, мы можем очень близко подобраться к этому, не нарушая правил XHTML. Фактически, мы можем использовать вложенный невидимый элемент, который будет доступен в дереве DOM из JavaScript, но не повлияет на отображение страницы.

 

Требования:

  • пользовательский метод в сценарии сервера для генерации HTML-элементов для отображения данных на сервере
  • пользовательский метод для чтения значений из HTML-элементов на странице

Обе эти пользовательские методики могут быть извлечены в серверную часть и JavaScript API, который можно использовать повторно.

 

Описание:

  1. Для каждого фрагмента кода HTML (который помещает компонент в сгенерированную страницу), создайте вложенный элемент div со специальным классом «config», которому присвоен стиль css «display: none;» :

    • div с классом «config» будет невидимым в документе и не будет влиять на то, как генерируется страница: 
      <div class="my-javascript-button">
          <div class="config" style="display: none;">
          </div>
      </div>
  2. Внутри div с классом «config» вы можете поместить любые элементы DOM, идентифицировать их с помощью атрибута класса, а затем присвоить им значение, либо как один из допустимых атрибутов, либо как тело элемента:
    <div class="my-javascript-button">
        <div class="config" style="display: none;">        
            <div class="title">My title</div>
            <a class="url" href="http://my.url"/>
            <ul class="listOfTargets" >
                <li>target1</li>
                <li>target2</li>
            </ul>
        </div>
    </div>
  3. Если вам нужно хранить вложенные объекты, вы также можете вкладывать элементы или даже вкладывать список элементов. Фактически вы можете создать любую HTML-структуру, которая будет каким-то образом хранить нужные значения. Вы можете сделать его более читабельным, используя соответствующие элементы HTML (ul для списков, a для URL), или всегда использовать элементы div.
  4. Во внешнем файле JavaScript определите инициализатор, который можно выполнить для инициализации конфигурации для всех целевых элементов на html-странице. Этот инициализатор найдет все целевые элементы, и для каждого из них он найдет элемент «config» и прочитает значения из поддерева DOM этого элемента.
  5. Функция инициализатора будет выполнена один раз после загрузки html-страницы (например, с помощью jQuery’s $ (document) .ready ()). Лучше всего выполнить инициализатор во внешнем файле JavaScript и убедиться, что он выполняется только один раз (вы можете использовать глобальную переменную или отметить все элементы конфигурации при первом выполнении, чтобы конфигурация читалась только один раз).

 

Полученные результаты:

  • Конфигурация всегда хранится в целевом элементе html, независимо от того, поддерживает ли браузер javascript (хотя это довольно неактуально, поскольку конфигурация без javascript бесполезна)
  • конфигурация декларативна, поэтому ее можно проанализировать любым альтернативным методом или даже внешним HTML-анализатором (например, в модульных тестах)
  • связь между элементом html и его конфигурацией видна с помощью инспектора DOM прямо внутри элемента html
  • нет стандартного способа записи и чтения конфигурации в и из элементов HTML
  • может быть медленнее, чем прямое преобразование данных сервера в JSON

Как это решение довольно приятно и читабельно.
Это может быть достаточно или даже желательно в некоторых случаях. Тем не менее, он имеет некоторые недостатки по сравнению с решением Simple JSON: код легче читать, но его написание занимает больше времени, а также чтение конфигурации с помощью JavaScript может выполняться медленнее, чем анализ JSON.

Поэтому, как всегда, есть золотая середина, которая решит все.
Или есть?

Решение Golden Mean — декларативный подход с JSON


Идея здесь состоит в том, чтобы стандартизировать способ хранения данных в рамках вложенного элемента конфигурации, используемого в декларативном решении.
Вместо написания config с использованием элементов DOM мы бы использовали JSON, который легко читается с помощью JavaScript. 

 

Требования:

  • Как и в решении Simple JSON, это решение требует поддержки JSON в серверном скрипте, но не использует случайные числа и не требует добавления JavaScript для инициализации компонента.
  • Как и в декларативном решении, требуется некоторый пользовательский код. Однако на этот раз никакой специальной методики на стороне сервера не требуется, а на стороне клиента требуется только функция для извлечения блока конфигурации в объявлении компонента (пара строк кода JavaScript для повторного использования)

 

Описание:


Это решение аналогично декларативному решению.
На самом деле, он также декларативный, но использует один простой трюк вместе с JSON:

Вместо того, чтобы кодировать данные в структуру HTML, мы можем закодировать конфигурацию в JSON и поместить ее как тело элемента config, например так:
<div class="my-javascript-button">
    <div class="config" style="display: none;">
        ${it as JSON}     </div>
</div>

JSON должен быть совместим с XHTML и не должен нарушать его структуру.
Если мы не уверены, лучше поместить код JSON в блок CDATA.