Разработка приложений SharePoint 2013 с помощью TypeScript

в 5:20, , рубрики: sharepoint, Веб-разработка

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

В этом посте я покажу как TypeScript поможет разрабатывать приложения для SharePoint 2013. В SharePoint 2013 были улучшены возможности разработки клиентских приложений на JavaScript. Это касается не только API, доступных на клиенте, но и механизмов поставки и развертывания приложений, инструментов разработчика. Более того, многие функции самого SharePoint 2013 реализованы и могу быть кастомизированы с помощью JavaScript.

SharePoint 2013 предлагает два вида API для использования на клиентской стороне: Client-Side Object Model (CSOM) и REST API. REST API позволяет манипулировать объектами на сервере используя REST (OData) веб-сервис. CSOM представляет из себя набор классов, семантически эквивалентных серверной объектной модели SharePoint. CSOM доступна как для JavaScript (также называют JSOM – JavaScript Object Model), так и для .NET. Но в JavaScript, в отличие от .NET, недоступны метаданные и типизация. В этой статье будет рассмотрено именно применение JSOM.

TypeScript позволяет описать типы для JSOM и использовать статическую проверку типов и intellisense при разработке приложений. К сожалению готовых определений типов для SharePoint 2013 в открытом доступе нет.

Я и Андрей Маркеев создали проект на CodePlex, в котором сделали определения типов и кучу примеров приложений на TypeScript для SharePoint 2013. Ссылка на проект — http://sptypescript.codeplex.com/

Пример приложения

Для примера создам приложение, позволяющее отслеживать время на рабочем месте.

Разработка приложений SharePoint 2013 с помощью TypeScript

Подготовка

Для начала необходимо:

Для того чтобы при сборке проекта выполнялась компиляция TypeScript необходимо добавить в .csproj файл следующие элементы:

<PropertyGroup>
    <TypeScriptTarget>ES3</TypeScriptTarget>
    <TypeScriptIncludeComments>true</TypeScriptIncludeComments>
    <TypeScriptSourceMap>true</TypeScriptSourceMap>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath32)MicrosoftVisualStudiov$(VisualStudioVersion)TypeScriptMicrosoft.TypeScript.targets" />
 
Библиотеки и определения

Визуальный интерфейс будет создан с помощью библиотеки knockoutjs с расширением koLite.

Для того чтобы использовать эти библиотеки в проекте необходимо добавить следующие NuGet пакеты:

  • KoLite (knockoutjs добавится автоматически)
  • jquery.TypeScript.DefinitelyTyped
  • knockout.TypeScript.DefinitelyTyped
  • kolite.TypeScript.DefinitelyTyped

Последние три пакета представляют из себя .d.ts файлы, которые описывают типы для TypeScript.

Для работы с JSOM в TypeScript надо добавить в проект файл SharePoint.d.ts, который можно найти по ссылке. NuGet пакет будет доступен в ближайшее время.

Загрузка скриптов по требованию
В SharePoint есть свой загрузчик скриптов по требованию в классе SP.SOD. Подробное описание можно найти в этом посте.

Код загрузчика скриптов приложения:

///<reference path="typings/SharePoint.d.ts" />
///<reference path="typings/jquery/jquery.d.ts" />
///<reference path="typings/knockout/knockout.d.ts" />

/// <reference path="ViewModel.ts" />


$(() => {
    SP.SOD.registerSod('ViewModels', _spPageContextInfo.webServerRelativeUrl + '/Scripts/ViewModel.js');
    SP.SOD.registerSodDep('ViewModels', 'sp.js');

    SP.SOD.executeFunc('ViewModels', null, () => {
        var vm = new ViewModels.Model(SP.ClientContext.get_current());
        ko.applyBindings(vm);
    });
});
Модель представления

Разметка страницы приложения:

<div>
    <p data-bind="text:message"></p>
    <button data-bind="text:buttonText, command: checkInOut, visible:isLoaded" style="display:none;"/>
</div>

Используется плагин koLite для асинхронных команд.

Код модели представления:

module ViewModels {
    export class Model {
        constructor(public context: SP.ClientContext) {
            this.isLoaded = ko.observable(false);
            this.message = ko.observable('');
            this.buttonText = ko.observable('');

            this.checkInOut = ko.asyncCommand({
                canExecute: (isExecuting) => !isExecuting && this.isLoaded(),
                execute: this.executeCheckInOut
            });

            this.init();
        }

        public message: KnockoutObservableString;
        public buttonText: KnockoutObservableString;
        public checkInOut: KoliteCommand;
        public isLoaded: KnockoutObservableBool;

        //...
    }
}

Все типы описаны в .d.ts файлах и проверяются при компиляции.Инициализация модели
JSOM при выполнении формирует очередь команд, отправляемых на сервер функцией SP.ClientContext.executeQueryAsync. executeQueryAsync принимает два коллбека, первый вызывается в случае успешного завершения, второй в случае неудачи. Внимание, указатель this портится внутри коллбеков функции executeQueryAsync, но если указывать коллбеки в виде лямбд, то TS заботливо генерирует код, который сохраняет указатель this.

private init() {
    this.list = this.context.get_web().get_lists().getByTitle('Log');
    var items = this.list.getItems(SP.CamlQuery.createAllItemsQuery());
    this.context.load(items);

    this.context.executeQueryAsync(
        () => {
            this.processItems(items);
            this.setData();
            this.isLoaded(true);
        },
        (sender, args) => alert(args.get_message()));
};

Запрос множества элементов в JSOM возвращает не массив, а коллекцию объектов, реализующую интерфейс IEnumerable, хотя внутри объекта лежит массив. Это все вызвано тем, что большая часть клиентской объектной модели сгенерирована из серверной объектной модели, и все коллекции требуют специальный паттерн для обхода. Он 100% соответствует коду .NET для обработки IEnumerable коллекций.

Обработка результатов запроса:

private processItems(items: SP.ListItemCollection) {
    this.hoursSubmitted = 0;
    var enumerator = items.getEnumerator();
    while (enumerator.moveNext()) {
        var item = <SP.ListItem>enumerator.get_current();
        var author = <SP.FieldUserValue>item.get_item('Author');
        //Filter by current user
        if (author.get_lookupId() == _spPageContextInfo.userId) {
            var dateCompleted = item.get_item('DateCompleted');
            if (dateCompleted) {
                this.hoursSubmitted += item.get_item('DurationInHours');
            } else {
                this.curentItem = item;
            }
        }
    }
}

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

Обработка команд
В зависимости от текущего состояния модели выполняется Check-In или Check-Out

private executeCheckInOut(complete: () => void ) {
    if (this.curentItem) {
        this.checkOut(complete);
    } else {
        this.checkIn(complete);
    }
};

Операция Check-In заключается в создании нового элемента в списке SharePoint, без указания времени завершения.

private checkIn(complete: () => void ) {
    var item = this.list.addItem(new SP.ListItemCreationInformation());
    item.set_item('StartDate', new Date());
    item.update();

    this.context.executeQueryAsync(
        () => {
            this.curentItem = item;
            this.setData();
            complete();
        },
        (sender, args) => {
            alert(args.get_message());
            complete();
        });
}

Противоположная операция – Check-Out – заполняет значения времени завершения и продолжительности в часах.

private checkOut(complete: () => void ) {
    var startedDate = <Date>this.curentItem.get_item('StartDate');
    var dateCompleted = new Date();
    var hours = (dateCompleted.getTime() - startedDate.getTime()) / (1000 * 60 * 60);

    this.curentItem.set_item('DateCompleted', dateCompleted);
    this.curentItem.set_item('DurationInHours', hours);
    this.curentItem.update();

    this.context.executeQueryAsync(
        () => {
            this.curentItem = null;
            this.hoursSubmitted += hours;
            this.setData();
            complete();
        },
        (sender, args) => {
            alert(args.get_message());
            complete();
        });
}

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

Заключение

Полный код примера вы можете скачать по ссылке. Также рекомендую посмотреть код проекта и примеры использования определений TypeScript для SharePoint (source code), найдете много интересного.

Кстати сам код примера будет работать и в SharePoint 2010, но придется создать другой проект и по-другому развертывать артефакты решения, чтобы все вместе заработало.

А в следующий раз я расскажу как можно кастомизировать формы и представления списков в SharePoint 2013, и тоже с помощью TypeScript.

Автор: gandjustas

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js