ignore import { Injectable , PLATFORM_ID , Inject } from '@angular/core' ; import { isPlatformBrowser } from '@angular/common' ; import { MasonryOptions , Masonry as IMasonry , AnimationOptions , ImagesLoadedConstructor , MasonryGridItem } from './ng-masonry-grid.interface' ; import { Observable , Subscriber , empty } from 'rxjs' ; declare var require ; @ Injectable ( ) export class NgMasonryGridService { el : any ; masonryOptions : MasonryOptions ; animationOptions : AnimationOptions ; items : Array < any > = [ ] ; itemsCount : number = 0 ; itemsRenderedCount : number = 0 ; didScroll : boolean ; resizeTimeout : any ; useAnimation : boolean ; isAnimate = false ; public _msnry : IMasonry ; _onScrollHandler : any ; _onResizeHandler : any ; useImagesLoaded : boolean ; imagesLoaded : ImagesLoadedConstructor ; docElem = window . document . documentElement ; /**
     * Default animation options of grid items on scroll
     */ animationDefaults : AnimationOptions = { animationEffect : 'effect-1' , minDuration : 0 , maxDuration : 0 , viewportFactor : 0 } ; /**
     * Default masonry options
     */ masonryDefaults : MasonryOptions = { itemSelector : '[ng-masonry-grid-item], ng-masonry-grid-item, [ng-masonry-grid-item].animate, ng-masonry-grid-item.animate' , initLayout : false , addStatus : 'append' } ; constructor ( @ Inject ( PLATFORM_ID ) private _platformId ) { this . _onScrollHandler = this . _onScrollFn . bind ( this ) ; this . _onResizeHandler = this . _resizeHandler . bind ( this ) ; } getViewportH ( ) { let client = this . docElem [ 'clientHeight' ] , inner = window [ 'innerHeight' ] ; if ( client < inner ) { return inner ; } else { return client ; } } scrollY ( ) { return window . pageYOffset || this . docElem . scrollTop ; } getOffset ( el ) { let offsetTop = 0 , offsetLeft = 0 ; do { if ( ! isNaN ( el . offsetTop ) ) { offsetTop += el . offsetTop ; } if ( ! isNaN ( el . offsetLeft ) ) { offsetLeft += el . offsetLeft ; } } while ( el = el . offsetParent ) ; return { top : offsetTop , left : offsetLeft } ; } inViewport ( el , h = 0 ) { let elH = el . offsetHeight , scrolled = this . scrollY ( ) , viewed = scrolled + this . getViewportH ( ) , elTop = this . getOffset ( el ) . top , elBottom = elTop + elH ; return ( elTop + elH * h ) <= viewed && ( elBottom - elH * h ) >= scrolled ; } extend ( a , b ) { for ( let key in b ) { if ( b . hasOwnProperty ( key ) ) { a [ key ] = b [ key ] ; } } return a ; } param el param masonryOptions param useAnimation param animationOptions param useImagesLoaded public init ( el , masonryOptions , useAnimation ? , animationOptions ? , useImagesLoaded ? ) { this . useAnimation = useAnimation ; this . el = el ; this . isAnimate = this . useAnimation || ( animationOptions ? true : false ) ; this . useImagesLoaded = useImagesLoaded ; this . masonryOptions = this . extend ( this . masonryDefaults , masonryOptions ) ; this . animationOptions = this . extend ( this . animationDefaults , animationOptions ) ; return this . _init ( ) ; } public initializeMasonry ( _element , options ) { const Masonry = require ( 'masonry-layout' ) ; return new Masonry ( _element , options ) ; } private _init ( ) { if ( this . isAnimate ) { this . el . classList . add ( this . animationOptions . animationEffect ) ; } if ( this . useImagesLoaded ) { this . imagesLoaded = require ( 'imagesloaded' ) ; } if ( isPlatformBrowser ( this . _platformId ) ) { return this . _initMasonry ( ) ; } return null ; } private _initMasonry ( ) { this . _msnry = this . initializeMasonry ( this . el , this . masonryOptions ) ; if ( this . isAnimate && this . _msnry ) { window . addEventListener ( 'scroll' , this . _onScrollHandler , false ) ; window . addEventListener ( 'resize' , this . _onResizeHandler , false ) ; /**
             * Remove scroll animations to remove conflicts between Masonry Transitions and Scroll Animations
             */ this . _msnry . on ( 'layoutComplete' , ( items ) => { Array . prototype . slice . call ( this . el . children ) . forEach ( ( element ) => { element . classList . remove ( 'animate' ) ; } ) ; } ) ; this . _msnry . on ( 'removeComplete' , ( items ) => { Array . prototype . slice . call ( this . el . children ) . forEach ( ( element ) => { element . classList . remove ( 'animate' ) ; } ) ; this . _msnry . layout ( ) ; } ) ; } return this . _msnry ; } private _onScrollFn ( ) { let self = this ; if ( ! this . didScroll ) { this . didScroll = true ; setTimeout ( ( ) => { self . _scrollPage ( ) ; } , 100 ) ; } } private _scrollPage ( ) { let self = this ; let items = this . _msnry . items . map ( ( item ) => item . element ) ; if ( items . length ) { items . forEach ( ( el , i ) => { if ( this . inViewport ( el , self . animationOptions . viewportFactor ) ) { let perspY = this . scrollY ( ) + this . getViewportH ( ) / 2 ; self . el . style . WebkitPerspectiveOrigin = '50% ' + perspY + 'px' ; self . el . style . MozPerspectiveOrigin = '50% ' + perspY + 'px' ; self . el . style . perspectiveOrigin = '50% ' + perspY + 'px' ; if ( self . animationOptions . minDuration && self . animationOptions . maxDuration ) { let randDuration = ( Math . random ( ) * ( self . animationOptions . maxDuration - self . animationOptions . minDuration ) + self . animationOptions . minDuration ) + 's' ; el . style . WebkitAnimationDuration = randDuration ; el . style . MozAnimationDuration = randDuration ; el . style . animationDuration = randDuration ; } el . classList . add ( 'animate' ) ; } else { el . classList . remove ( 'animate' ) ; } } ) ; } this . didScroll = false ; } private _resizeHandler ( ) { let self = this ; Array . prototype . slice . call ( this . el . children ) . forEach ( ( element ) => { element . classList . remove ( 'animate' ) ; } ) ; function delayed ( ) { self . _scrollPage ( ) ; self . resizeTimeout = null ; } if ( this . resizeTimeout ) { clearTimeout ( this . resizeTimeout ) ; } this . resizeTimeout = setTimeout ( delayed , 500 ) ; } /**
     * Check if total grid items are redered in the DOM
     */ private _checkTotalRendered ( ) { ++ this . itemsRenderedCount ; if ( this . itemsRenderedCount === this . itemsCount ) { window . removeEventListener ( 'scroll' , this . _onScrollHandler , false ) ; } } /**
     * On Destroy remove Scroll and Resize event Listeners
     */ public onDestory ( ) { window . removeEventListener ( 'scroll' , this . _onScrollHandler , false ) ; window . removeEventListener ( 'resize' , this . _onResizeHandler , false ) ; } /**
     * Remove scroll animations
     */ public removeAnimation ( ) { if ( this . isAnimate ) { Array . prototype . slice . call ( this . el . children ) . forEach ( ( element ) => { element . classList . remove ( 'animate' ) ; } ) ; } } param element public add ( element ) { let addStatus = this . masonryOptions . addStatus . toLowerCase ( ) ; if ( this . masonryOptions . gutter ) { element . style . marginBottom = this . masonryOptions . gutter + 'px' ; } if ( this . el . hasChildNodes ( ) && this . el . contains ( element ) ) { this . el . removeChild ( element ) ; } if ( this . useImagesLoaded ) { setTimeout ( ( ) => { this . imagesLoaded ( element , ( instance ) => { ++ this . itemsRenderedCount ; this . items . push ( element ) ; if ( addStatus === 'prepend' ) { if ( this . _msnry . items . length !== 0 ) { this . el . insertBefore ( element , this . _msnry . items [ 0 ] . element ) ; } else { this . el . appendChild ( element ) ; } } else if ( addStatus === 'append' ) { this . el . appendChild ( element ) ; } else { this . el . appendChild ( element ) ; } setTimeout ( ( ) => { if ( addStatus === 'prepend' ) { if ( this . _msnry . items . length !== 0 ) { this . _msnry . prepended ( element ) ; } else { this . _msnry . appended ( element ) ; } } else if ( addStatus === 'append' ) { this . _msnry . appended ( element ) ; } else { this . _msnry . addItems ( element ) ; } this . _msnry . layout ( ) ; } , 0 ) ; } ) ; } , 0 ) ; } else { setTimeout ( ( ) => { ++ this . itemsRenderedCount ; this . items . push ( element ) ; if ( addStatus === 'prepend' ) { if ( this . _msnry . items . length !== 0 ) { this . el . insertBefore ( element , this . _msnry . items [ 0 ] . element ) ; } else { this . el . appendChild ( element ) ; } } else if ( addStatus === 'append' ) { this . el . appendChild ( element ) ; } else { this . el . appendChild ( element ) ; } setTimeout ( ( ) => { if ( addStatus === 'prepend' ) { if ( this . _msnry . items . length !== 0 ) { this . _msnry . prepended ( element ) ; } else { this . _msnry . appended ( element ) ; } } else if ( addStatus === 'append' ) { this . _msnry . appended ( element ) ; } else { this . _msnry . addItems ( element ) ; } this . _msnry . layout ( ) ; } , 50 ) ; } , 0 ) ; } } param element public addOrderItem ( element ) { let addStatus = this . masonryOptions . addStatus . toLowerCase ( ) ; if ( this . masonryOptions . gutter ) { element . style . marginBottom = this . masonryOptions . gutter + 'px' ; } if ( this . useImagesLoaded ) { setTimeout ( ( ) => { this . imagesLoaded ( element , ( instance ) => { } ) ; } , 0 ) ; } else { setTimeout ( ( ) => { if ( addStatus === 'prepend' ) { if ( this . _msnry . items . length !== 0 ) { this . el . insertBefore ( element , this . _msnry . items [ 0 ] . element ) ; } else { this . el . appendChild ( element ) ; } } else if ( addStatus === 'append' ) { this . el . appendChild ( element ) ; } else { this . el . appendChild ( element ) ; } setTimeout ( ( ) => { if ( this . itemsRenderedCount === this . itemsCount ) { if ( addStatus === 'prepend' ) { if ( this . _msnry . items . length !== 0 ) { this . _msnry . prepended ( element ) ; this . _msnry . layout ( ) ; } else { this . _msnry . appended ( element ) ; this . _msnry . layout ( ) ; } } else if ( addStatus === 'append' ) { this . _msnry . appended ( element ) ; this . _msnry . layout ( ) ; } else { this . _msnry . addItems ( element ) ; this . _msnry . layout ( ) ; } } } , 500 ) ; } , 0 ) ; } } /**
     * Re order Masonry items to original order
     */ public reorderMasonryItems ( ) { if ( this . itemsRenderedCount === this . itemsCount ) { let reorderItems = this . items . sort ( ( a , b ) => { return a . dataset . count - b . dataset . count ; } ) ; while ( this . el . hasChildNodes ( ) ) { this . el . removeChild ( this . el . lastChild ) ; } setTimeout ( ( ) => { reorderItems . forEach ( ( item ) => { this . el . appendChild ( item ) ; this . _msnry . appended ( item ) ; } ) ; if ( this . _msnry ) { this . _msnry . reloadItems ( ) ; this . _msnry . layout ( ) ; } } , 100 ) ; } } param value public setAddStatus ( value ) { this . masonryOptions . addStatus = value ; } param item public removeItem ( item ) { this . removeAnimation ( ) ; if ( item ) { item . classList . remove ( 'animate' ) ; const obsv = Observable . create ( observer => { let count = item . getAttribute ( 'data-count' ) ; let index = this . _msnry . items . findIndex ( ( masonryItem ) => { return masonryItem . element . getAttribute ( 'data-count' ) === count ; } ) ; setTimeout ( ( ) => { const elem = this . _msnry . items [ index ] . element ; this . _onTransitionEnd ( observer , elem ) ; const indx = this . items . findIndex ( ( element ) => { return element . dataset . count === item . dataset . count ; } ) ; this . items . splice ( indx , 1 ) ; } , 20 ) ; } ) ; return obsv ; } return empty ( ) ; } /**
     * Remove first grid item from the Masonry List
     */ public removeFirstItem ( ) { this . removeAnimation ( ) ; if ( this . _msnry . items . length ) { this . _msnry . items [ 0 ] . element . classList . remove ( 'animate' ) ; const obsv = Observable . create ( observer => { this . _onTransitionEnd ( observer , this . _msnry . items [ 0 ] . element ) ; this . items . splice ( 0 , 1 ) ; } ) ; return obsv ; } return empty ( ) ; } /**
     * Empty the Masonry list
     */ public removeAllItems ( ) { this . removeAnimation ( ) ; const obsv = Observable . create ( observer => { setTimeout ( ( ) => { let items = [ ] ; this . _msnry . items . forEach ( ( masonryItem , index ) => { items . push ( { element : masonryItem . element , index } ) ; this . addTransition ( masonryItem . element ) ; } ) ; const elem = this . _msnry . items [ this . _msnry . items . length - 1 ] . element ; const transitionEnd = ( ) => { observer . next ( items ) ; setTimeout ( ( ) => { this . _msnry . reloadItems ( ) ; this . _msnry . layout ( ) ; this . items = [ ] ; this . itemsCount = 0 ; this . itemsRenderedCount = 0 ; } , 10 ) ; elem . removeEventListener ( 'transitionend' , transitionEnd , false ) ; } ; elem . addEventListener ( 'transitionend' , transitionEnd , false ) ; if ( this . masonryOptions . transitionDuration === "0" ) { transitionEnd ( ) ; } } , 10 ) ; } ) ; return obsv ; } param elem public addTransition ( elem ) { elem . style . transition = 'transform ' + this . masonryOptions . transitionDuration + ', opacity ' + this . masonryOptions . transitionDuration ; elem . style . transform = 'scale(0.001)' ; elem . style . opacity = '0' ; } param observer param elem private _onTransitionEnd ( observer , elem ) { if ( elem ) { this . addTransition ( elem ) ; const transitionEnd = ( ) => { let isindex = Array . prototype . slice . call ( this . el . children ) . findIndex ( ( element ) => { return element . getAttribute ( 'data-count' ) === elem . getAttribute ( 'data-count' ) ; } ) ; if ( isindex !== - 1 ) { observer . next ( { element : elem , index : isindex } ) ; } setTimeout ( ( ) => { this . _msnry . reloadItems ( ) ; this . _msnry . layout ( ) ; } , 10 ) ; elem . removeEventListener ( 'transitionend' , transitionEnd , false ) ; } ; elem . addEventListener ( 'transitionend' , transitionEnd , false ) ; } } }