В SharePoint 2013 довольно богатая клиентская библиотека. Ранее я уже писал об аналоге jQuery и об Ajax библиотеках. Одна из важнейших частей для разработки масштабного JavaScipt приложения – загрузчик скриптов. Такое тоже есть в SharePoint.

Почему необходимо управлять загрузкой скриптов

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

Но важнейшая задача управления загрузкой скриптов – создание полноценной модульности для javascript. Ведь сам язык не предоставляет инструмента описания связей между модулями и гарантий, что зависимости будут загружены до выполнения скрипта.

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

В мире веб-разработки фактически стандарт для управляемой загрузки скриптов – библиотека requirejs. Но в ней есть критический недостаток, её придумал не Microsoft, поэтому в SharePoint своя библиотек для загрузки скриптов. Называется она Script On Demand.

Script On Demand

Вся библиотека содержится в классе SP.SOD в файле init.js. Script On Demand спроектирован как неинтрузивная библиотека, в отличие от requirejs, то есть скрипты будут работать и без нее. Это важно в App Parts, которые по сути отдельные страницы в iframe. Там нет смысла откладывать загрузку скриптов.

Документация по SP.SOD есть на MSDN, но она там, мягко говоря, ужасная. В проекте SPTypeScript есть качественные определения типов для SP.SOD, они будут более полезны (SP.Init.d.ts).

Добавление скриптов на страницу

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

У контрола ScriptLink несколько атрибутов:

  • Name – указывает имя скрипта. Если указать только имя файла, то скрипт будет загружаться из _layouts. Поддерживаются url tokens, такие как ~site и ~sitecollection.
  • Localizable – если стоит значение true и указано только имя скрипта, то скрипт будет за загружаться из _layouts/{langId}.
  • LoadAfterUI – если стоит значение true, скрипт будет добавлен в конец страницы (независимо от местоположения контрола ScriptLink) и загрузка начнется после рисования всего интерфейса.
  • OnDemand – если указано значение false или атрибут отсутствует, то генерируется такая разметка:
    <script type="text/javascript">
    // <![CDATA[
    document.write(
        '<script type="text/javascript" src="script.js"></' 
        + 'script>');
    // ]]>
    </script>
    
    
    Если же указано значение true, то генерируемый код такой:
    <script type="text/javascript">
    RegisterSod("script.js", "script.js");
    </script>
    
    
    Метод RegisterSod это SP.SOD.registerSod. Загрузка такого скрипта не начнется, пока не будет вызвана явно с помощью класса SP.SOD.

Если вы делаете серверную веб-часть или контрол без разметки, то вам могут пригодиться статические методы класса ScriptLink:

К сожалению класс ScriptLink не доступен в Sandbox, поэтому обязательно необходимо пользоваться классом SP.SOD.

Внимание: никогда в веб-частях и контролах не генерируйте тег script с атрибутом src. Ваш контрол или веб-часть могут быть добавлены более одного раза на страницу и скрипты будут загружаться несколько раз. Если же вы вызовете SP.SOD.registerSod несколько раз, то скрипт будет загружен только один раз (свойство идемпотентности).

SP.SOD.registerSod(fileName: string, url: string): void;

В первом параметре указывается имя зависимости, а во втором – url скрипта.

Ожидание загрузки скрипта

Если вы воспользовались ScriptLink с атрибутом OnDemand=false, и вам надо дождаться окончания его загрузки, чтобы вызвать свой код, то вам нужен метод

SP.SOD.executeOrDelayUntilScriptLoaded(func: () => void , depScriptFileName: string): bool

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

Вызов этой функции часто нужен для ожидания окончания загрузки файла sp.js, содержащего JSOM.

SP.SOD.executeOrDelayUntilScriptLoaded(function() {
    var ctx = SP.Context.gt_current();
    //Do something
}, 'sp.js');

Увы, эта функция теряет this.

Загрузка по требованию

Если вам необходимо загрузить скрипт по требованию, то для этого есть целых два метода:

  • SP.SOD.execute(fileName: string, functionName: string, args?: any[]): void;
  • SP.SOD.executeFunc(fileName: string, typeName: string, fn: () => void ): void;

Пример вызовов (результат одинаковый):

SP.SOD.executeFunc('mQuery.js', 'm$', function() {
     m$.ready(function (){ });
}

SP.SOD.execute('mQuery.js', 'm$.ready', [function (){ }] );


Есть еще один метод, который загружает сразу несколько скриптов:
SP.SOD.loadMultiple(keys: string[], fn: () => void): void;

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

Зависимости между скриптами

Чтобы сделать зависимость между скриптами надо вызвать метод:

SP.SOD.registerSodDep(fileName: string, dependentFileName: string): void;

В дальнейшем, если вызвать загрузка скрипта с именем fileName, то перед ним будет загружен скрипт с именем dependentFileName. Эта функция идемпотентна, как и SP.SOD.registerSod.

Использование SOD в своих скриптах

Чтобы загружать свой скриптовый файл с помощью SOD, надо в конце скрипта вызывать метод

SP.SOD.notifyScriptLoadedAndExecuteWaitingJobs(scriptFileName: string): void;

Так как SOD неинтрузивная библиотека, то хорошим тоном будет писать так:

if ( SP && SP.SOD ) {
    SP.SOD.notifyScriptLoadedAndExecuteWaitingJobs("script.js");
}

Если вы используете внешние скрипты и хотите их подключить к SOD, то потребуется написать хелпер (typescript):

function loadExternalScript(key: string, scriptPath: string) {
    var scriptNode = document.createElement('script');
    scriptNode.type = 'text/javascript';
    scriptNode.src = scriptPath;

    var headNode = document.getElementsByTagName('head');
    if (headNode[0] != null)
        headNode[0].appendChild(scriptNode);

    var callback = (e: Event) => {
        SP.SOD.notifyScriptLoadedAndExecuteWaitingJobs(key);
    };
    scriptNode.onreadystatechange = callback;
    scriptNode.onload = callback;
}

SOD и MDS

SharePoint 2013 принес еще одно новшество- Minimal Download Strategy. Эта стратегия заставляет браузер перезагружать не всю страницу, а частями. Такая загрузка снижает трафик, но плохо влияет на скрипты. Потому что теперь страница может быть обновлена без перезагрузки скриптов.

Для того, чтобы функция была вызвана после ajax перезагрузки страницы (без перезагрузки скриптов), надо зарегистрировать её с помощью

function RegisterModuleInit(scriptFileName: string, initFunc: () => void ): void;

Первый параметр – url скрипта, указанный в SOD, второй – функция, которую надо вызывать при ajax перезагрузке.

Ковыряя исходники скриптов SharePoint, я нашел интересную вещь. Функция SP.SOD.notifyScriptLoadedAndExecuteWaitingJobs сама вызывает RegisterModuleInit. В качестве initFunc передается функция с именем $_global_!scriptName!. !scriptName! формируется путем отрезания расширения (после последней точки) и заменой всех точек на символы подчеркивания. Но это работает только для скриптов в _layouts/15.

Publish\Subscribe

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

Publish\Subscribe состоит из трех методов:

  • SP.SOD.notifyEventAndExecuteWaitingJobs(eventName: string, args?: any[]): void;
    Этот метод вызывает события с указанными параметрами.
  • SP.SOD.executeOrDelayUntilEventNotified(func: Function, eventName: string): bool;
    Эта функция создает подписку на событие с параметрами и вызывается если событие уже наступило.
  • SP.SOD.delayUntilEventNotified(func: Function, eventName: string): void;
    Эта функция только создает подписку.

Заключение

Обязательно используйте SOD в своих проектах. Это позволит делать модульные скрипты и корректно взаимодействовать со скриптами SharePoint.

Теги : js, sp2013, javascript, SharePoint