вторник, 21 октября 2014 г.

Система событий JSGrid

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

Подписка на события


Для подписки на события в JsGrid существует метод AttachEvent объекта SP.JsGrid.JsGridControl:

/** Attach event handler to a particular event type */
AttachEvent(eventType: JsGrid.EventType, fnOnEvent: { (args: IEventArgs): void }): void;
/** Detach a previously set event handler */
DetachEvent(eventType: JsGrid.EventType, fnOnEvent): void;

Существует целых 30 различных типов событий (eventType) – при клике на ячейку, правом клике, двойном клике, начале редактирования ячейки, окончании редактирования, после вставки данных из буфера обмена, при выборе записи, при изменении текущего выделения и т.д.

Вот полный список:

export enum EventType {
    OnCellFocusChanged,
    OnRowFocusChanged,
    OnCellEditBegin,
    OnCellEditCompleted,
    OnRightClick,
    OnPropertyChanged,
    OnRecordInserted,
    OnRecordDeleted,
    OnRecordChecked,
    OnCellErrorStateChanged,
    OnEntryRecordAdded,
    OnEntryRecordCommitted,
    OnEntryRecordPropertyChanged,
    OnRowErrorStateChanged,
    OnDoubleClick,
    OnBeforeGridDispose,
    OnSingleCellClick,
    OnInitialChangesForChangeKeyComplete,
    OnVacateChange,
    OnGridErrorStateChanged,
    OnSingleCellKeyDown,
    OnRecordsReordered,
    OnBeforePropertyChanged,
    OnRowEscape,
    OnBeginRenameColumn,
    OnEndRenameColumn,
    OnPasteBegin,
    OnPasteEnd,
    OnBeginRedoDataUpdateChange,
    OnBeginUndoDataUpdateChange
}

Как видите, события могут быть крайне полезны при создании интерактивных интерфейсов на основе JsGrid. Открываются действительно интересные возможности: мало того, что у нас Excel в браузере, мы еще вдобавок можем сделать его интерактивным! :)

Теперь про обработчики событий. На вход в обработчик поступает некоторый объект IEventArgs. В зависимости от типа события, объект этот будет разный и содержать разные сведения. Есть хорошие новости: в процессе подготовки дефинишенов, мне удалось вычислить все возможные типы IEventArgs и описать их!

Вот они:

export interface IEventArgs { }
export module EventArgs {
    export class OnEntryRecordAdded implements IEventArgs {
        constructor(recordKey: number);
        recordKey: number;
    }

    export class CellFocusChanged implements IEventArgs {
        constructor(newRecordKey: number, newFieldKey: string, oldRecordKey: number, oldFieldKey: string);
        newRecordKey: number;
        newFieldKey: string;
        oldRecordKey: number;
        oldFieldKey: string;
    }
    export class RowFocusChanged implements IEventArgs {
        constructor(newRecordKey: number, oldRecordKey: number);
        newRecordKey: number;
        oldRecordKey: number;
    }
    export class CellEditBegin implements IEventArgs {
        constructor(recordKey: number, fieldKey: string);
        recordKey: number;
        fieldKey: string;
    }
    export class CellEditCompleted implements IEventArgs {
        constructor(recordKey: number, fieldKey: string, changeKey: JsGrid.IChangeKey, bCancelled: boolean);
        recordKey: number;
        fieldKey: string;
        changeKey: JsGrid.IChangeKey;
        bCancelled: boolean;
    }
    export class Click implements IEventArgs {
        constructor(eventInfo, context: JsGrid.ClickContext, recordKey: number, fieldKey: string);
        eventInfo: any;
        context: JsGrid.ClickContext;
        recordKey: number;
        fieldKey: string;
    }
    export class PropertyChanged implements IEventArgs {
        constructor(recordKey: number, fieldKey: string, oldProp: SP.JsGrid.Internal.PropertyUpdate, newProp: SP.JsGrid.Internal.PropertyUpdate, propType: SP.JsGrid.IPropertyType, changeKey: SP.JsGrid.IChangeKey, validationState: SP.JsGrid.ValidationState);
        recordKey: number;
        fieldKey: string;
        oldProp: SP.JsGrid.Internal.PropertyUpdate;
        newProp: SP.JsGrid.Internal.PropertyUpdate;
        propType: SP.JsGrid.IPropertyType;
        changeKey: SP.JsGrid.IChangeKey;
        validationState: SP.JsGrid.ValidationState;
    }
    export class RecordInserted implements IEventArgs {
        constructor(recordKey, recordIdx, afterRecordKey, changeKey);
        recordKey: number;
        recordIdx: number;
        afterRecordKey: number;
        changeKey: JsGrid.IChangeKey;
    }
    export class RecordDeleted implements IEventArgs {
        constructor(recordKey, recordIdx, changeKey);
        recordKey: number;
        recordIdx: number;
        changeKey: JsGrid.IChangeKey;
    }
    export class RecordChecked implements IEventArgs {
        constructor(recordKeySet: SP.Utilities.Set, bChecked: boolean);
        recordKeySet: SP.Utilities.Set;
        bChecked: boolean;
    }
    export class OnCellErrorStateChanged implements IEventArgs {
        constructor(recordKey, fieldKey, bAddingError, bCellCurrentlyHasError, bCellHadError, errorId);
        recordKey: number;
        fieldKey: string;
        bAddingError: boolean;
        bCellCurrentlyHasError: boolean;
        bCellHadError: boolean;
        errorId: number;
    }
    export class OnRowErrorStateChanged implements IEventArgs {
        constructor(recordKey, bAddingError, bErrorCurrentlyInRow, bRowHadError, errorId, message);
        recordKey: number;
        bAddingError: boolean;
        bErrorCurrentlyInRow: boolean;
        bRowHadError: boolean;
        errorId: number;
        message: string;
    }
    export class OnEntryRecordCommitted implements IEventArgs {
        constructor(origRecKey: string, recordKey: number, changeKey: JsGrid.IChangeKey);
        originalRecordKey: number;
        recordKey: number;
        changeKey: JsGrid.IChangeKey
    }
    export class SingleCellClick implements IEventArgs {
        constructor(eventInfo, recordKey: number, fieldKey: string);
        eventInfo: any;
        recordKey: number;
        fieldKey: string;
    }
    export class PendingChangeKeyInitiallyComplete implements IEventArgs {
        constructor(changeKey: JsGrid.IChangeKey);
        changeKey: JsGrid.IChangeKey
    }
    export class VacateChange implements IEventArgs {
        constructor(changeKey: JsGrid.IChangeKey);
        changeKey: JsGrid.IChangeKey
    }
    export class GridErrorStateChanged implements IEventArgs {
        constructor(bAnyErrors: boolean);
        bAnyErrors: boolean;
    }
    export class SingleCellKeyDown implements IEventArgs {
        constructor(eventInfo, recordKey: number, fieldKey: string);
        eventInfo: any;
        recordKey: number;
        fieldKey: string;
    }
    export class OnRecordsReordered implements IEventArgs {
        constructor(recordKeys: string[], changeKey: JsGrid.IChangeKey);
        reorderedKeys: string[];
        changeKey: JsGrid.IChangeKey;
    }
    export class OnRowEscape implements IEventArgs {
        constructor(recordKey: number);
        recordKey: number;
    }
    export class OnEndRenameColumn implements IEventArgs {
        constructor(columnKey: string, originalColumnTitle: string, newColumnTitle: string);
        columnKey: string;
        originalColumnTitle: string;
        newColumnTitle: string;
    }
    export class OnBeginRedoDataUpdateChange implements IEventArgs {
        constructor(changeKey: JsGrid.IChangeKey);
        changeKey: JsGrid.IChangeKey
    }
    export class OnBeginUndoDataUpdateChange implements IEventArgs {
        constructor(changeKey: JsGrid.IChangeKey);
        changeKey: JsGrid.IChangeKey
    }

}

Дефинишены входят в состав проекта TypeScript Definitions for SharePoint 2013, и вы можете посмотреть текущее состояние дефинишенов JsGrid по вот этой прямой ссылке. Так что если вам нужно понять что такое например IChangeKey и другие сложные типы, изучайте на здоровье.

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

SP.JsGrid.EventType.OnPropertyChanged


Событие OnPropertyChanged срабатывает каждый раз, когда значение какой-либо из ячеек изменилось. Очевидно, можно использовать это событие для обновления строки.
Для того, чтобы JsGrid собственно обновил строку, можно использовать метод RefreshRow объекта JsGridControl:

/** Re-render the specified row in the view. */
RefreshRow(recordKey: number): void;

Теперь разберемся с EventArgs. Определение JsGrid.EventArgs.PropertyChanged выглядит следующим образом:

export class PropertyChanged implements IEventArgs {
    constructor(recordKey: number, fieldKey: string, oldProp: SP.JsGrid.Internal.PropertyUpdate, newProp: SP.JsGrid.Internal.PropertyUpdate, propType: SP.JsGrid.IPropertyType, changeKey: SP.JsGrid.IChangeKey, validationState: SP.JsGrid.ValidationState);
    recordKey: number;
    fieldKey: string;
    oldProp: SP.JsGrid.Internal.PropertyUpdate;
    newProp: SP.JsGrid.Internal.PropertyUpdate;
    propType: SP.JsGrid.IPropertyType;
    changeKey: SP.JsGrid.IChangeKey;
    validationState: SP.JsGrid.ValidationState;
}

Здесь самое главное, что у нас есть recordKey и fieldKey, которые позволяют однозначно определить, какая строка и какая ячейка были изменены.

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

control.AttachEvent(SP.JsGrid.EventType.OnPropertyChanged, function (args) {
    control.RefreshRow(args.recordKey)
});
, где control – это экземпляр SP.JsGrid.JsGridControl.

Обратите внимание, что среди значений объекта EventArgs есть довольно интересные вещи, такие как ValidationState, который может быть Invalid, и в зависимости от которого вы также можете свою какую-то кастомную логику реализовывать.

Очень важно также, что доступно не только новое значение свойства, но и предыдущее. Определение SP.JsGrid.Internal.PropertyUpdate выглядит вот как:

export class PropertyUpdate {
    constructor(data: any, localized: string);
    data: any;
    localized: string;
}

А важно знать предыдущее значение, например потому что часто требуется выполнять какие-то действия только если значение изменилось например с А на В, но не с Б на В – распространенный пример, это состояние задач в багтрекере: если статус меняется с “Открыто” на “Проверено” минуя фазу “Выполнено” (которую обычно тестируют), тут точно надо бить тревогу! :)

Кстати, AttachEvent (в отличие от SetDelegate) не требует, чтобы его выполняли именно в момент инициализации грида, как минимум в случае события OnPropertyChanged и других событий, на которых я тестировал.

Заключение


События – это неотъемлемая часть любого API. В JsGrid событий много, подписываться на них легко, и даже описания EventArgs у нас теперь есть благодаря скромному мне :)

Так что тут всё отлично, мин я не нашел, используем! :)

1 комментарий:

  1. Здравствуйте, Андрей!
    А существует ли событие, которое происходит после полной загрузки JSGrid или инициализации его компонентов?
    Дело в том, что необходимо выполнить скрипт сразу после загрузки страницы, содержащей JSGrid. Страница с JSGrid представляет собой стандартную страницу PWA.
    Если использовать _spBodyOnLoadFunctionNames, то при выполнении моего скрипта возникают ошибки, связанные с тем, что необходимые мне компоненты JSGrid еще не инициализированы.
    Возможно ли как-то обойти данную проблему?

    ОтветитьУдалить

Внимание! Реклама и прочий спам будут беспощадно удаляться.