import "jqueryui" ; import { override } from "@microsoft/decorators" ; import { Log } from "@microsoft/sp-core-library" ; import { BaseFieldCustomizer , IFieldCustomizerCellEventParameters } from "@microsoft/sp-listview-extensibility" ; import { SPPermission } from "@microsoft/sp-page-context" ; import { sp } from "@pnp/sp" ; import * as $ from "jquery" ; import * as strings from "SpfxItemOrderFieldCustomizerStrings" ; import { IChangedRow } from "./IChangedRow" ; import { IOrderedRow } from "./IOrderedRow" ; import { ISpfxItemOrderFieldCustomizerProperties } from "./ISpfxItemOrderFieldCustomizerProperties" ; import styles from "./SpfxItemOrderFieldCustomizer.module.scss" ; const TIMEOUTDURATION_INIT = 100 ; const LISTSURFACE = '.ms-List-surface' ; const LISTROWCONTAINER = '.ms-List-cell' ; const INDICATORICONCLASS = 'ms-Icon--Pin' ; const LOADINGICONCLASS = 'ms-Icon--Refresh' ; const DISABLEDICONCLASS = 'ms-Icon--Unpin' ; const INDICATORTEXTVALUE = '≡' ; const LOADINGTEXTVALUE = '֍' ; const DISABLEDTEXTVALUE = 'Ø' ; const LOG_SOURCE = 'SpfxItemOrderFieldCustomizer' ; export default class SpfxItemOrderFieldCustomizer extends BaseFieldCustomizer < ISpfxItemOrderFieldCustomizerProperties > { private _timeoutId_Init : number ; private _rowOrder : Array < number > ; private _rowMap : Array < IOrderedRow > ; private _orderField : string ; private _useIcons : boolean ; @ override public onInit ( ) { this . _orderField = this . properties . OrderField || 'Order' ; this . _useIcons = this . properties . ShowIcons != undefined ? this . properties . ShowIcons : true ; this . _rowMap = new Array < IOrderedRow > ( ) ; return super . onInit ( ) . then ( _ => { sp . setup ( { spfxContext : this . context } ) ; } ) ; } @ override public onRenderCell ( event ) { if ( this . _timeoutId_Init ) { clearTimeout ( this . _timeoutId_Init ) ; } event . domElement . classList . add ( 'ms-font-l' ) ; event . domElement . classList . add ( `${ styles . SpfxItemOrder }` ) ; event . domElement . classList . add ( 'ms-Fabric' ) ; event . domElement . classList . add ( 'ms-SPLegacyFabricBlock' ) ; event . domElement . classList . add ( 'ms-fontColor-themePrimary' ) ; if ( this . context . pageContext . list . permissions . hasPermission ( SPPermission . editListItems ) ) { if ( this . _useIcons ) { event . domElement . innerHTML = `<i class="ms-Icon ${ INDICATORICONCLASS } ${ styles . reorderField }" aria-hidden="true" title="${ strings . ReorderTooltip }"></i>` ; } else { event . domElement . innerHTML = `<span class="${ styles . reorderField }" title="${ strings . ReorderTooltip }">${ INDICATORTEXTVALUE }</span>` ; } this . _rowMap . push ( { Id : event . listItem . getValueByName ( 'ID' ) , Order : event . listItem . getValueByName ( this . _orderField ) } ) ; this . _timeoutId_Init = setTimeout ( this . onCellsRendered . bind ( this ) , TIMEOUTDURATION_INIT ) ; } else { if ( this . _useIcons ) { event . domElement . innerHTML = `<i class="ms-Icon ${ DISABLEDICONCLASS }" aria-hidden="true" title="${ strings . NoPermissionsTooltip }"></i>` ; } else { event . domElement . innerHTML = `<span aria-hidden="true" title="${ strings . NoPermissionsTooltip }">${ DISABLEDTEXTVALUE }</span>` ; } } } /** Once all the cells are rendered (icons), and the key values stored in the
     *   internal rowMap object, we setup the jQuery UI Sortable interface
     */ public onCellsRendered ( ) { $ ( document ) . ready ( ( ) => { $ ( LISTSURFACE ) . sortable ( { stop : this . onOrderChanged . bind ( this ) , items : LISTROWCONTAINER , } ) ; this . _rowOrder = SpfxItemOrderFieldCustomizer . getRowOrder ( ) ; } ) ; } @ override public onDisposeCell ( event ) { super . onDisposeCell ( event ) ; } /** Handles the jQuery UI Sortable stopsort event */ public onOrderChanged ( e , ui ) { $ ( LISTSURFACE ) . sortable ( 'disable' ) ; let newOrder = SpfxItemOrderFieldCustomizer . getRowOrder ( ) ; if ( SpfxItemOrderFieldCustomizer . hasChanged ( this . _rowOrder , newOrder ) ) { this . saveChanges ( ) ; } else { $ ( LISTSURFACE ) . sortable ( 'enable' ) ; } } /** Saves the new Order value for all reordered list items */ public saveChanges ( ) { let newOrder = SpfxItemOrderFieldCustomizer . getRowOrder ( ) ; let dirtyRows = SpfxItemOrderFieldCustomizer . changedRows ( this . _rowOrder , newOrder ) ; let itemBatch = sp . createBatch ( ) ; dirtyRows . forEach ( ( row ) => { SpfxItemOrderFieldCustomizer . showLoading ( row . listIndex , this . _useIcons ) ; let props = { } ; props [ this . _orderField ] = this . _rowMap [ row . position ] . Order ; sp . web . lists . getById ( this . context . pageContext . list . id . toString ( ) ) . items . getById ( this . _rowMap [ row . listIndex ] . Id ) . inBatch ( itemBatch ) . update ( props ) ; } ) ; itemBatch . execute ( ) . then ( ( ) => { SpfxItemOrderFieldCustomizer . hideLoading ( this . _useIcons ) ; this . _rowOrder = newOrder ; $ ( LISTSURFACE ) . sortable ( 'enable' ) ; } ) . catch ( ( error ) : void => { Log . error ( LOG_SOURCE , error ) ; if ( console && console . log ) { console . log ( error ) ; } } ) ; } /** Grabs the current row order from the paged listview
     *  This returns an array of the original list-index value in the order they currently are in
     */ public static getRowOrder ( ) { return ( < any > $ ( LISTSURFACE ) ) . sortable ( 'toArray' , { attribute : 'data-list-index' } ) . map ( ( value ) => { return parseInt ( value ) ; } ) ; } /** Quickly compares order arrays to see if any of the values are different */ public static hasChanged ( prevOrder , newOrder ) { for ( let i = 0 ; i < newOrder . length ; i ++ ) { if ( newOrder [ i ] !== prevOrder [ i ] ) { return true ; } } return false ; } /** Evaluates the order arrays and only returns those rows which have changed */ public static changedRows ( prevOrder , newOrder ) { let diffRows = new Array < IChangedRow > ( ) ; for ( let i = 0 ; i < newOrder . length ; i ++ ) { if ( newOrder [ i ] !== prevOrder [ i ] ) { diffRows . push ( { listIndex : newOrder [ i ] , position : i } ) ; } } return diffRows ; } /** Shows the loading indicator for the specific row */ public static showLoading ( listIndex , useIcons ) { if ( useIcons ) { $ ( LISTROWCONTAINER + `[data-list-index=${ listIndex }] .${ styles . reorderField }` ) . removeClass ( INDICATORICONCLASS ) . addClass ( LOADINGICONCLASS ) . addClass ( 'isSpinning' ) . addClass ( styles . spinning ) ; } else { $ ( LISTROWCONTAINER + `[data-list-index=${ listIndex }] .${ styles . reorderField }` ) . text ( LOADINGTEXTVALUE ) . addClass ( 'isSpinning' ) . addClass ( styles . spinning ) ; } } /** Returns all loading indicators back to the original icon */ public static hideLoading ( useIcons ) { if ( useIcons ) { $ ( `.${ styles . reorderField }.isSpinning` ) . removeClass ( 'isSpinning' ) . removeClass ( LOADINGICONCLASS ) . removeClass ( styles . spinning ) . addClass ( INDICATORICONCLASS ) ; } else { $ ( `.${ styles . reorderField }.isSpinning` ) . removeClass ( 'isSpinning' ) . removeClass ( styles . spinning ) . text ( INDICATORTEXTVALUE ) ; } } }