Ajax

Последовательность вызовов при перехвате методов

Здесь, прежде чем рассказывать, необходимо сделать небольшое пояснение

Важной, можно сказать, «идеологической» характеристикой расширений является их автономность. То есть расширения должны проектироваться так, чтобы не зависеть друг от друга.

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

Когда вы подключаете расширения к типовой конфигурации, образуется «многослойный пирог». В основании этого пирога находится типовая конфигурация, а на его вершине – последнее подключенное расширение.

Что в конфигураторе, что в режиме 1С:Предприятие, последнее подключенное расширение находится в списке последним.

Таким образом, в этом примере внизу находится типовая, наверху находится Расширение2, а между ними – Расширение1. Каждое следующее расширение перехватывает (расширяет) то, что находится под ним.

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

Пример 1

Например, если в двух расширениях перехвачен (обрамлён) один и тот же типовой метод, то последовательность вызова обработчиков будет следующая:

  • Сначала будет вызван перехватчик из Расширения2, потому что оно сверху. Это будет перехватчик &Перед, потому что у него такая аннотация;
  • Затем будет вызван перехватчик из Расширения1, потому что оно следующее в пироге. Это будет снова &Перед, потому что у него такая аннотация;
  • После этого будет вызван типовой метод, потому что больше нет перехватчиков, препятствующих его исполнению;
  • Затем, в обратной последовательности «пирога», будут вызваны перехватчик &После из Расширения1 и перехватчик &После из Расширения2.

На этом примере можно хорошо понять следующую особенность: если в одном из перехватчиков возникает необработанное исключение, то вся цепочка прерывается, и исключение продолжает распространяться.

Пример 2

Если в перехватчиках используется метод ПродолжитьВызов(), то действует тот же самый принцип «пирога».

  • Сначала будет вызван перехватчик из Расширения3, потому что оно сверху. Это будет перехватчик &Вместо, потому что у него такая аннотация;
  • При попытке вызвать типовой метод, будет анализироваться оставшийся «пирог». Анализироваться он будет точно таким же образом, как было описано в предыдущем примере;
  • В результате исполнение кода вернётся в перехватчик &Вместо, а по его завершении – в типовую конфигурацию.

Пример 3

Важным для понимания моментом является тот факт, что при перекрытии с помощью аннотации &Вместо, по факту перекрывается не только вызов основного метода, но также и перехватчиков, находящихся ниже в «пироге».

В этом примере будет выполнен только перехватчик &Вместо из Расширения2. Потому что он перекрывает собой типовой метод, то есть весь «пирог», который находится под ним.

Пример 4

Это, по сути, вариация на тему второго примера, но когда под верхним расширением находится расширение, также «прокидывающее» вниз вызов типовой процедуры.

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

Общие модули

В расширении вы можете создавать любые собственные общие модули. Существует только два ограничения:

  • Они не должны быть глобальными серверными;
  • Они не должны быть привилегированными.

Когда вы расширяете общий модуль типовой конфигурации, тоже есть аналогичные ограничения:

  • Нельзя заимствовать глобальные серверные модули;
  • Код из вашего расширения будет исполняться только в непривилегированном режиме (если иное не разрешено в профиле безопасности).

Сама операция заимствования глобального серверного модуля не запрещена в дереве конфигурации, но на этапе обновления конфигурации базы данных вы получите ошибку, и обновление выполнено не будет.

Серверные методы расширяются не всегда

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

Если прикладное решение работает в файловом варианте или в клиент-серверном варианте без профилей безопасности, то при подключении вашего расширения:

  • В обычном режиме исполнения встроенного языка — будут расширяться все методы типового решения, и клиентские, и серверные;
  • В безопасном режиме исполнения встроенного языка — будут расширяться только клиентские методы и серверные обработчики форм. К остальным серверным процедурам / функциями расширение применяться не будет.

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

Для этого в профиль безопасности мы добавили несколько новых свойств.

Самое простое из них – это флажок к расширению всех модулей в группе Разрешен полный доступ. Он «одним махом» разрешает расширение серверного контекста.

Есть и более точная настройка с помощью полей Доступные для расширения модули и Недоступные для расширения модули. Мы предполагаем, что вы будет использовать их следующим образом:

  • Если полный доступ к расширениям вы не разрешали, то в поле Доступные для расширения модули вы перечисляете имена тех модулей, для которых расширение серверного контекста допустимо и не страшно;
  • Если вы разрешили полный доступ к расширениям, то в поле Недоступные для расширения модули вы перечисляете некоторые модули, в которых всё-таки не нужно допускать расширения серверного контекста. 

Другой вариант использования: срезаем вершки

Мой пример использования – это функция, задействованная в 5% случаев. Сделайте страницу легче для 95% пользователей, которые не пользуются этой функцией, а саму функцию создайте такой, чтобы ее можно было сравнительно быстро добавить для тех пользователей, кому она нужна.

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

Например, делаете matchMedia проверку для некоторых запросов медиа-контента и определяете, что параметры экрана пользовательского устройства и его возможности таковы, что можно включить некоторые дополнительные модули.

Здорово, сделайте это с помощью нескольких параллельных Ajax вызовов!

РедакцияПеревод статьи «Multiple Simultaneous Ajax Requests (with one callback) in jQuery»

Перехват обработчиков событий и собственные обработчики в модулях форм

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

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

Внешне перехватчик события в модуле формы выглядит следующим образом:

То есть аннотация не используется, а тип перехватчика указывается в палитре свойств. Делается это довольно просто. При создании в расширении обработчика по кнопке «лупа» открывается диалог. Он позволяет вам, помимо контекста, указать тип перехватчика (Перед, После или Вместо).

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

Если вы перекрываете типовой обработчик (Вместо), то это будет просто точка.

Если вы создаёте перехватчик Перед или После, то это будет точка рядом с вертикальной чертой. Местонахождение точки, перед или после черты, обозначает тип перехватчика. А кроме этого в палитре свойств появляется второе (пустое) поле рядом с этим событием. С его помощью вы можете задать парный перехватчик, если есть необходимость обрамить типовой обработчик парой Перед — После.

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

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

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

Как это сделать наилучшим образом?

Ajax вызовы в jQuery обеспечивают обратные вызовы:

jQuery

$.ajax({
  statusCode: {
    url: "/feature",
    success: function() {
      // успешное завершение Ajax
    }
  }
});

Или «отложенный» способ, на этот раз с использованием сокращенного метода $.get():

jQuery

$.get("/feature/").done(function() {
  // успешное завершение Ajax 
});

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

jQuery

// Получаем HTML
$.get("/feature/", function(html) {
  // Получаем CSS
  $.get("/assets/feature.css", function(css) {
    // Получаем JavaScript
    $.getScript("/assets/feature.js", function() {
       // Все готово, поэтому...
       // Добавляем CSS на страницу
       $("<style />").html(css).appendTo("head");
       //Добавляем HTML на страницу
       $("body").append(html);
    });
  });
});

Здесь мы ждем, пока все функции выполнятся, прежде чем добавлять что-либо на страницу. Таким образом, к тому моменту, когда пользователь увидит что-то, все будет уже готово к выполнению. Возможно, некоторым из читателей такое решение не понравится, но я делал подобные вещи ранее.

По крайней мере, это не лишено смысла и работает. В чем же проблема? Медленное выполнение.

Один запрос… ожидаем, пока завершится…, другой запрос… ожидаем, пока завершится… еще запрос…ожидаем, пока завершится…вперед.

Было бы быстрее, если бы могли сделать:

Все три запроса выполняются параллельно…,ожидаем, пока все три завершатся…вперед.

Мы можем воспользоваться здесь Deferred /Promises действием. Я уверен, что для многих это материал JavaScript 101, но эти вещи ускользали от меня в течение долгого времени, а более сложный механизм Promise непонятен до сих пор.

В нашем простом примере, мы можем использовать метод jQuery $.when(), который принимает список таких Deferred объектов (все jQuery Ajax методы возвращают Deferred объекты) и затем обеспечить одиночный обратный вызов.

jQuery

$.when(
  // Отложенный объект (возможно Ajax запрос),
  // Отложенный объект (возможно Ajax запрос),
  // Отложенный объект (возможно Ajax запрос)
}.then(function() {
  // Все было решено (или отклонено), делаем, что нужно
});

Таким образом, наш адский callback можно переписать следующим образом:

jQuery

$.when(
  // Получаем HTML
  $.get("/feature/", function(html) {
    globalStore.html = html;
  }),
  // Получаем CSS
  $.get("/assets/feature.css", function(css) {
    globalStore.css = css;
  }),
  // Получаем JS
  $.getScript("/assets/feature.js")
).then(function() {
  // Все готово, поэтому…
  // Добавляем CSS на страницу
  $("<style />").html(globalStore.css).appendTo("head");
  // Добавляем HTML на страницу
  $("body").append(globalStore.html);
});

Перехват вызовов методов

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

Необходимость перехвата того или иного типового метода вы полностью описываете в расширяющем модуле. Для этого мы ввели во встроенный язык новый структурный элемент – аннотацию. С помощью аннотации, расположенной перед определением метода, вы указываете, какой именно типовой метод перехватывает процедура/функция, и каким именно образом. Например:

Аннотация &Перед(«Процедура1») означает, что перехватывается типовая процедура с именем Процедура1. Имя аннотации Перед означает, что сначала будет выполнена ваша процедура-перехватчик Расш_Проц1(), а затем — типовая Процедура1().

В настоящий момент мы добавили в платформу три возможных имени аннотаций.

Аннотация &Перед

Аннотация с таким именем означает, что ваш перехватчик будет выполнен до того, как начнётся выполнение типового метода.

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

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

Аннотация &Вместо

Эта аннотация как раз и реализует возможность полного перекрытия типового метода. То есть типовой метод вообще не будет выполнен. Вместо него выполнится только ваш перехватчик.

На одну и ту же типовую процедуру вы можете установить в своём расширении одну из следующих комбинаций перехватчиков:

  • &Перед;
  • &После;
  • &Вместо;
  • &Перед и &После.

Последняя комбинация перехватчиков (&Перед и &После) будет исполняться следующим образом:

Если же вы перехватываете типовую функцию, а не процедуру, то вы можете использовать только перехватчик &Вместо.

Вызов метода, перекрытого аннотацией &Вместо

Получается некоторая несправедливость. Процедуру вы можете перекрыть или обрамить. А функцию – только полностью перекрыть.

Чтобы избавиться от этой несправедливости, мы реализовали во встроенном языке новый метод – ПродолжитьВызов(). Если вы вызовете этот метод внутри своей функции-перехватчика, то исполнится та функция, которую вы перекрыли, после чего исполнение кода вернётся в ваш перехватчик:

На встроенном языке такая функция-перехватчик может выглядеть следующим образом:

Таким образом, ваша функция-перехватчик разбивается на две части. Ту часть, которая исполняется перед исполнением типовой функции, и ту, которая исполняется после типовой.

Метод ПродолжитьВызов() вы можете использовать не только при перекрытии функций, но и при перекрытии процедур. В этом случае результат его применения будет по смыслу такой же, как при использовании пары перехватчиков &Перед и &После. Разница будет лишь в том, что в этом случае ваша часть «до» и ваша часть «после» будут существовать внутри одного контекста. В некоторых ситуациях это может быть удобно. На встроенном языке такая процедура-перехватчик может выглядеть следующим образом:

Рейтинг
( Пока оценок нет )
Editor
Editor/ автор статьи

Давно интересуюсь темой. Мне нравится писать о том, в чём разбираюсь.

Понравилась статья? Поделиться с друзьями:
Люкс-хост
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: