Статьи

Простой одностраничный интерфейс без сохранения состояния с ItsNat v1.3

ItsNat является LGPL лицензирован рамки AJAX , направленные на разработку приложений и веб — сайтов одностраничный интерфейс SEO совместимые, в философии  одной страницы интерфейса манифеста 

До того, как нормальный режим работы версии v1.3 был основан на состоянии сервера, ItsNat в основном имитирует браузер Java W3C на сервере, сервер хранит копию DOM страницы пользователя, изменения DOM на сервере автоматически передаются клиенту для синхронизации. с сервером через код JavaScript, сгенерированный на лету, обычно в результате запросов AJAX (или запросов на основе <script>). В результате получается веб-разработка, основанная на чистых шаблонах HTML и коде Java W3C DOM, в основном тот же тип программирования, который вы делаете с помощью JavaScript на клиенте с использованием API-интерфейсов W3C, но в Java в том же пространстве памяти, где собираются данные, то есть в памяти сервера.

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

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

Давайте посмотрим на простой, но мощный пример без сохранения состояния.

Создайте пустое веб-приложение, используйте предпочитаемую IDE, в самом простом подходе просто скопируйте jar дистрибутива ItsNat в папку WEB-INF / lib.

Прежде всего, мы создаем новый сервлет с именем servletstless, используем вашу IDE и заменяем код следующим:

public class servletstless extends HttpServletWrapper
{

  @Override
  public void init(ServletConfig config) throws ServletException
  {
  super.init(config);

  ItsNatHttpServlet itsNatServlet = getItsNatHttpServlet();

  String pathPrefix = getServletContext().getRealPath("/");

     pathPrefix += "/WEB-INF/pages/manual/";

  ItsNatDocumentTemplate docTemplate;

  docTemplate = itsNatServlet.registerItsNatDocumentTemplate(

    "manual.stless.example","text/html",

    pathPrefix + "stless_example.html");

  docTemplate.addItsNatServletRequestListener(

      new StlessExampleInitialDocLoadListener());

  docTemplate.setEventsEnabled(false); // Stateless

  docTemplate = itsNatServlet.registerItsNatDocumentTemplate(

     "manual.stless.example.eventReceiver","text/html",

     pathPrefix + "stless_example_event_receiver.html");

  docTemplate.addItsNatServletRequestListener(

      new StatelessExampleForProcessingEventDocLoadListener());

  docTemplate.setEventsEnabled(false); // Stateless 

  ItsNatDocFragmentTemplate docFragDesc;

  docFragDesc = itsNatServlet.registerItsNatDocFragmentTemplate(

      "manual.stless.example.fragment","text/html",

      pathPrefix + "stless_example_fragment.html"); 

  }

}

Следующий фрагмент кода регистрирует шаблон начальной страницы:

  ItsNatDocumentTemplate docTemplate;

  docTemplate = itsNatServlet.registerItsNatDocumentTemplate(

    "manual.stless.example","text/html",

     pathPrefix + "stless_example.html");

  docTemplate.addItsNatServletRequestListener(

     new StlessExampleInitialDocLoadListener());

  docTemplate.setEventsEnabled(false); // Stateless

Обратите внимание на вызов setEventsEnabled (false), этот вызов метода конфигурации отключает клиент-сервер с удаленными событиями с сохранением состояния , поскольку для приема событий не требуется, чтобы ItsNatDocument хранился на сервере, ItsNat не сохранял документ в памяти или в сеансе, этот параметр необходимо, если вы хотите настоящий документ без гражданства.

Файл stless_example.html:

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"  xmlns:itsnat="http://itsnat.org/itsnat">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>ItsNat Stateless Example in Manual</title>
    <script>
    function sendEventStateless()
    {
        var counterElem = document.getElementById("counterId");
        var userEvt = document.getItsNatDoc().createEventStateless();
        userEvt.setExtraParam('counter',counterElem.firstChild.data);
        userEvt.setExtraParam('itsnat_doc_name','manual.stless.example.eventReceiver');        
        document.getItsNatDoc().dispatchEventStateless(userEvt,3 /*XHR_ASYNC_HOLD*/, 1000000);
    }    
    </script>    
    
</head>
<body>

    <h3>ItsNat Stateless Example in Manual</h3>

    <h4 id="presentationId" itsnat:nocache="true"></h4>
         
    <br /><br />
    <a href="javascript:sendEventStateless()">Send stateless event</a> 
        
    <br /><br />  
    <div>
        <div>Num. Events: <b id="counterId" itsnat:nocache="true">0</b></div>
    </div>
        
    <div>
        <div>
            <div id="insertHereId" /> 
        </div>
    </div>

    <br />
</body>
</html>

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

document.getItsNatDoc().dispatchEventStateless(userEvt,3/*XHR_ASYNC_HOLD*/, 1000000);

Мы понимаем, что существует другой вид удаленных событий: события без гражданства.

Этот метод принимает три параметра:

1) Объект события: следуя тем же соглашениям, что и в пользовательских событиях, описанных ранее в этом руководстве

2) Транспортный режим: целое число с теми же значениями констант org.itsnat.core.CommMode. Большую часть времени вы будете передавать 3 параметра (XHR_ASYNC_HOLD = AJAX асинхронные события в очереди).

3) Тайм-аут в миллисекундах

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

Эта строка интересна:

userEvt.setExtraParam('itsnat_doc_name','manual.stless.example.eventReceiver');   

ItsNat распознает этот стандартный параметр «загрузки документа» также в событии без состояния, когда при получении этого события без сохранения состояния на сервере поведение сервера ItsNat следующее:

1) Новый ItsNatDocument создается на основе указанного шаблона. Этот экземпляр документа не имеет состояния независимо от того, был ли настроен шаблон (setEventsEnabled (false) просто рекомендуется для ясности и во избежание загрузки с сохранением состояния с использованием обычных URL-адресов)

2) Обработка загрузки документа, как обычно, имеет радикальное отличие: конечное состояние DOM по окончании фазы загрузки не сериализуется как HTML, а исходный код JavaScript, созданный на этом этапе, игнорируется / теряется .

3) Глобальный прослушиватель событий, зарегистрированный в фазе загрузки, вызывающий ItsNatDocument.addEventListener (EventListener), выполняется, получая событие без сохранения состояния . Модификации DOM, выполненные на этом этапе, отправляются клиенту как возвращение события аналогично удаленному событию с отслеживанием состояния.

В обычном режиме ItsNat с сохранением состояния это поведение без состояния кажется бессмысленным, однако в нем есть много смысла…

Основная цель обработки событий без сохранения состояния:

1) Чтобы полностью или частично восстановить состояние клиента DOM на сервере: это является целью фазы загрузки, конечное состояние / дерево DOM документа после загрузки должно полностью или частично соответствовать состоянию клиента.

2) Измените соответствующим образом состояние DOM документа, чтобы сгенерировать необходимый JavaScript-код для приведения клиента в новое состояние: это является целью этапа обработки событий.

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

К счастью, вам не нужно полностью реконструировать клиентскую страницу на сервере, вы просто можете реконструировать только те части, которые должны быть изменены конкретным событием, кроме того, режим без состояний ItsNat является исключительным для внедрения новой разметки на клиентскую страницу.

Продолжая наш пример, Java-код для генерации начальной страницы:

public class StlessExampleInitialDocLoadListener 
   implements ItsNatServletRequestListener
{
  public StlessExampleInitialDocLoadListener()
  {
  }

  public void processRequest(ItsNatServletRequest request,
         ItsNatServletResponse response)
 {
  ItsNatHTMLDocument itsNatDoc = 
         (ItsNatHTMLDocument)request.getItsNatDocument();

   new StlessExampleInitialDocument(itsNatDoc);
  }

}

public class StlessExampleInitialDocument
{
  protected ItsNatHTMLDocument itsNatDoc;

  public StlessExampleInitialDocument(ItsNatHTMLDocument itsNatDoc)
  {
    this.itsNatDoc = itsNatDoc;

    HTMLDocument doc = itsNatDoc.getHTMLDocument();

    Text node = (Text)doc.createTextNode(
     "This the initial stateless page (not kept in server)");

    Element presentationElem = doc.getElementById("presentationId");
    presentationElem.appendChild(node); 
  }

}

Этот код является лишь предлогом, чтобы показать, что начальная страница является обычной страницей, созданной ItsNat (но без состояния), ничего особенного здесь нет.

Интересная вещь начинается с шаблона stless_example_event_receiver.html, зарегистрированного с именем manual.stless.example.eventReceiver, который будет обрабатывать события без сохранения состояния:

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"  xmlns:itsnat="http://itsnat.org/itsnat">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>ItsNat Stateless Document For Stateless Event Processing</title>
</head>
<body>
    
    <b id="counterId" itsnat:locById="true" itsnat:nocache="true">(num)</b>     
    
    <div id="insertHereId" itsnat:locById="true" itsnat:nocache="true" /> 
    
</body>
</html>

Прежде чем продолжить со связанным кодом Java, давайте остановимся на сравнении исходного шаблона страницы:

   <div>
       <div>Num. Events: <b id="counterId" itsnat:nocache="true">0</b></div>
   </div>

    <div>
        <div>
            <div id="insertHereId" /> 
        </div>
    </div>

и этот шаблон:

    <b id="counterId" itsnat:locById="true" itsnat:nocache="true">(num)</b>     
    <div id="insertHereId" itsnat:locById="true" itsnat:nocache="true" /> 

Обе пары элементов имеют одинаковый идентификатор, это не случайно, например, содержание элемента insertHereId исходного шаблона страницы будет таким же, как и у шаблона «обработчика событий», так как вы можете видеть только имя тега ( div) и атрибут id одинаковы, обратите внимание, как оба элемента расположены на разных местах (порядок разметки, относящихся к элементу body) на странице, далее мы объясним, что это является причиной того, что атрибут ItsNat locById установлен в true.

Код Java, связанный с этим шаблоном:

public class StatelessExampleForProcessingEventDocLoadListener 
		implements ItsNatServletRequestListener
{
  public StatelessExampleForProcessingEventDocLoadListener()
  {
  }
   
  public void processRequest(ItsNatServletRequest request,
       ItsNatServletResponse response)
  {
      new StatelessExampleForProcessingEventDocument(
			     (ItsNatHTMLDocument)request.getItsNatDocument()
        ,request,response);
  }
}

public class StatelessExampleForProcessingEventDocument 
		implements Serializable,EventListener
{
    protected ItsNatHTMLDocument itsNatDoc;
    protected Element counterElem;
    
    public StatelessExampleForProcessingEventDocument(
        ItsNatHTMLDocument itsNatDoc, 
			     ItsNatServletRequest request, 
        ItsNatServletResponse response)
    {
        this.itsNatDoc = itsNatDoc;
        
        if (!itsNatDoc.isCreatedByStatelessEvent())
            throw new RuntimeException(
         				"Only to test stateless, must be loaded by a stateless event");

        // Counter node with same value (state) than in client:
        String currCountStr = 
              request.getServletRequest().getParameter("counter");
        int counter = Integer.parseInt(currCountStr);
        
        HTMLDocument doc = itsNatDoc.getHTMLDocument();
        this.counterElem = doc.getElementById("counterId");
       ((Text)counterElem.getFirstChild()).setData(String.valueOf(counter));
        
        itsNatDoc.addEventListener(this);
    }

    public void handleEvent(Event evt)
    {
        ItsNatEventDOMStateless itsNatEvt = (ItsNatEventDOMStateless)evt;
        
        Text counterText = (Text)counterElem.getFirstChild();
        String currCountStr = counterText.getData();
        int counter = Integer.parseInt(currCountStr);
        counter++;
        counterText.setData(String.valueOf(counter));        
        
        Document doc = itsNatDoc.getDocument();        
        
        Element elemParent = doc.getElementById("insertHereId");
        ScriptUtil scriptGen = itsNatDoc.getScriptUtil();
        String elemRef = scriptGen.getNodeReference(elemParent);
        ClientDocument clientDoc = itsNatEvt.getClientDocument();
        clientDoc.addCodeToSend(elemRef + ".innerHTML = '';");        
        clientDoc.addCodeToSend("alert('Currently inserted fragment removed before');");        
                
        ItsNatServlet servlet = 
             itsNatDoc.getItsNatDocumentTemplate().getItsNatServlet();  
        ItsNatHTMLDocFragmentTemplate docFragTemplate = 
			(ItsNatHTMLDocFragmentTemplate)servlet.getItsNatDocFragmentTemplate("manual.stless.example.fragment");  

        DocumentFragment docFrag = 
             docFragTemplate.loadDocumentFragmentBody(itsNatDoc);  
  
        elemParent.appendChild(docFrag); // docFrag is empty now  
        
        // Umm we have to celebrate/highlight this insertion 
        Element child1 = ItsNatTreeWalker.getFirstChildElement(elemParent);
        Element child2 = ItsNatTreeWalker.getNextElement(child1);
        Text textChild2 = (Text)child2.getFirstChild();
        Element bold = doc.createElement("i");
        bold.appendChild(textChild2); // is removed from child2
        child2.appendChild(bold);  
        child2.setAttribute("style","color:red"); 
		      // <h3 style="color:red"><i>Inserted!</i></h3>  
    }    
}

Таким образом, этот код обновляет элемент counterId текущим значением на странице и вставляет шаблон с именем manual.stless.example.fragment в элемент с идентификатором insertHereId в документ, загруженный для обработки события без сохранения состояния. Позже мы увидим, что эта вставка, наконец, происходит на последней странице клиента.

Разметка шаблона, зарегистрированного в manual.stless.example.fragment, выглядит следующим образом:

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"  
			xmlns:itsnat="http://itsnat.org/itsnat">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Fragment</title>
</head>
<body>

    <h4 style="color:green"><b>Fragment...</b></h4>
    <h3>Inserted!</h3>        
    
</body>
</html>

Поскольку этот шаблон вставляется, вызывая docFragTemplate.loadDocumentFragmentBody (itsNatDoc), игнорировать все, кроме содержимого <body>.

Давайте изучим код:

        if (!itsNatDoc.isCreatedByStatelessEvent())
            throw new RuntimeException(
			   "Only to test stateless, must be loaded by a stateless event");

Эта проверка позволяет избежать попыток загрузить этот документ, предназначенный для обработки событий без сохранения состояния, в качестве обычной страницы с указанием URL-адреса, содержащего параметр itsnat_document, с именем шаблона. Метод ItsNatDocument.isCreatedByStatelessEvent () возвращает значение true только в том случае, если документ был загружен как часть обработки события без сохранения состояния.

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

Обратите внимание, что мы получаем счетчик параметров с текущим целым числом в элемент counterId:

        // Counter node with same value (state) than in client:
        String currCountStr = request.getServletRequest().getParameter("counter");
        int counter = Integer.parseInt(currCountStr);
        
        HTMLDocument doc = itsNatDoc.getHTMLDocument();
        this.counterElem = doc.getElementById("counterId");
       ((Text)counterElem.getFirstChild()).setData(String.valueOf(counter));

С помощью этого кода мы «восстанавливаем» состояние DOM элемента counterId в клиенте.

Наконец в фазе загрузки:

  itsNatDoc.addEventListener(this);

Он регистрирует объект «this» в качестве прослушивателя событий для обработки события без сохранения состояния, вызывающего загрузку этого документа. После фазовой загрузки этот прослушиватель будет немедленно вызван и событие состояния не пройдено.

  public void handleEvent(Event evt)
  {
    ItsNatEventDOMStateless itsNatEvt = (ItsNatEventDOMStateless)evt;

Это приведение звучит знакомо, ItsNat расширяет события DOM другими видами событий, определенных ItsNat, в этом случае события без сохранения состояния являются новым видом «расширенного события DOM».

Интерфейс ItsNatEventDOMStateless наследуется от ItsNatEventStateless, объект, реализующий интерфейс ItsNatEventStateless, является «событием без состояния». Позже мы увидим случай, когда событие без сохранения состояния реализует ItsNatEventStateless, но не ItsNatEventDOMStateless.

Теперь пришло время по-настоящему войти в подход без сохранения ItsNat:

  Text counterText = (Text)counterElem.getFirstChild();
  String currCountStr = counterText.getData();
  int counter = Integer.parseInt(currCountStr);
  counter++;
  counterText.setData(String.valueOf(counter)); 

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

Больше действий:

  ItsNatDocument itsNatDoc = itsNatEvt.getItsNatDocument();
  Document doc = itsNatDoc.getDocument();
  Element elemParent = doc.getElementById("insertHereId");
  ScriptUtil scriptGen = itsNatDoc.getScriptUtil();
  String elemRef = scriptGen.getNodeReference(elemParent);
  ClientDocument clientDoc = itsNatEvt.getClientDocument();
  clientDoc.addCodeToSend(elemRef + ".innerHTML = '';"); 
  clientDoc.addCodeToSend("alert('Currently inserted fragment removed before');"); 

В этом случае ничего не делается в фазе загрузки, связанной с элементом insertHereId, потому что мы хотим снова и снова вставлять один и тот же DOM в этот элемент, и пользовательский код JavaScript легче очищать текущее состояние в клиенте (ItsNat в режиме без состояния ItsNat гораздо более рекомендуется ориентироваться на клиента и знание JavaScript).  

Этот код требует дополнительных пояснений, документ, который мы модифицируем, был создан на основе шаблона с именем manual.stless.example.eventReceiver, этот шаблон похож, но не идентичен manual.stless.example, несмотря на то, что сгенерированный JavaScript-код отправляется для изменения позже. 

Давайте еще раз повторим важные (динамические) элементы:

· Manual.stless.example

    <div>
        <div>Num. Events: <b id="counterId">0</b></div>
    </div>

    <div>
        <div>
            <div id="insertHereId" /> 
        </div>
    </div>

· Ma ual.stless.example.eventReceiver

  
    <b id="counterId" itsnat:locById="true" itsnat:nocache="true">(num)</b>  
    <div id="insertHereId" itsnat:locById="true" itsnat:nocache="true" /> 

Как вы знаете, локализация узла ItsNat основана на «подсчете узлов в дереве» от <html> до конкретного узла, конечно, локализация узла является разумной, и автоматическое кэширование происходит, чтобы избежать обхода дерева от корневого узла <html>. В любом случае, в некоторых случаях происходит полный обход, согласно предыдущей локализации элементов в первом шаблоне не то же самое, что и во втором шаблоне, если только мы явно не избегаем обхода сверху вниз от корня. Это является причиной атрибута ItsNat locById.

Атрибут locById указывает ItsNat использовать атрибут / свойство id в сгенерированном JavaScript для определения местоположения узла, просто вызывающего getElementById (). Используя этот трюк, вы можете создавать специальные более легкие шаблоны с достаточной разметкой, соответствующей состоянию DOM клиента.

Некоторый код остается объяснить:

  ItsNatServlet servlet = itsNatDoc.getItsNatDocumentTemplate().getItsNatServlet(); 

  ItsNatHTMLDocFragmentTemplate docFragTemplate =
  (ItsNatHTMLDocFragmentTemplate)servlet.getItsNatDocFragmentTemplate(
  "manual.stless.example.fragment"); 

  DocumentFragment docFrag = 
         docFragTemplate.loadDocumentFragmentBody(itsNatDoc); 
  elemParent.appendChild(docFrag); // docFrag is empty now 

  // Umm we have to celebrate/highlight this insertion
  Element child1 = ItsNatTreeWalker.getFirstChildElement(elemParent);
  Element child2 = ItsNatTreeWalker.getNextElement(child1);
  Text textChild2 = (Text)child2.getFirstChild();
  Element bold = doc.createElement("i");
  bold.appendChild(textChild2); // is removed from child2
  child2.appendChild(bold); 
  child2.setAttribute("style","color:red");
    // <h3 style="color:red"><i>Inserted!</i></h3> 

Этот традиционный код ItsNat вставляет измененную версию разметки внутри <body> загруженного шаблона manual.stless.example.fragment в элемент insertHereId. Интересной деталью этого кода является то, что большинство модификаций DOM происходят после вставки в документ (помните, что соответствующий код DOM JavaScript генерируется на лету), любой вовлеченный узел, child1, child2 и textChild2, должен быть расположен после вставки, если они находится через полное дерево из <html>?

Нет!

Как было сказано ранее, кэширование местоположения позволяет избежать полного обхода дерева сверху, например, ItsNat ищет родительские узлы, уже находящиеся в дереве и кэшированные, в этом случае узел insertHereId является кандидатом, или любой другой дочерний узел, кэшированный при вставке. Эти методы обеспечивают мощную серверно-ориентированную Java-обработку дерева DOM клиента за пределы простой одноразовой вставки.

Демо онлайн  (с еще большим примером).

Наслаждайтесь!