/**
 * Copyright (c) 2016-2018 Dmitry Panyushkin
 * Available under MIT license
 */ import * as ko from "knockout" ; import { ArrayPrototype , arraySlice , defineProperty , hasOwnProperty , isArray , PATCHED_KEY } from "./common-functions" ; import { prepareDeepValue } from "./observable-property" ; import { applyExtenders } from "./property-extenders" ; type ObsArray = ko . ObservableArray < any > & { [ fnName ] : Function ; } ; const deepArrayMethods = [ "pop" , "reverse" , "shift" , "sort" ] ; const allArrayMethods = [ ... deepArrayMethods , "push" , "splice" , "unshift" ] ; const deepObservableArrayMethods = [ "remove" , "removeAll" , "destroy" , "destroyAll" , "replace" , "subscribe" ] ; const allObservableArrayMethods = [ ... deepObservableArrayMethods , "replace" ] ; const allMethods = [ ... allArrayMethods , ... allObservableArrayMethods , "mutate" , "set" ] ; export function defineObservableArray ( instance , key , value , deep , expose ) { const obsArray = applyExtenders ( instance , key , ko . observableArray ( ) ) as ObsArray ; let insideObsArray = false ; defineProperty ( instance , key , { enumerable : true , get : obsArray , set : setter , } ) ; if ( expose ) { defineProperty ( instance , "_" + String ( key ) , { value : obsArray , } ) ; } setter ( value ) ; function setter ( newValue ) { const lastValue = obsArray . peek ( ) ; if ( lastValue !== newValue ) { if ( isArray ( lastValue ) ) { if ( hasOwnProperty ( lastValue , PATCHED_KEY ) ) { delete lastValue [ PATCHED_KEY ] ; allMethods . forEach ( ( fnName ) => { delete lastValue [ fnName ] ; } ) ; } } if ( isArray ( newValue ) ) { if ( hasOwnProperty ( newValue , PATCHED_KEY ) ) { newValue = [ ... newValue ] ; } if ( deep ) { for ( let i = 0 ; i < newValue . length ; ++ i ) { newValue [ i ] = prepareDeepValue ( newValue [ i ] , expose ) ; } } defineProperty ( newValue , PATCHED_KEY , { value : true , } ) ; patchArrayMethods ( newValue ) ; } } insideObsArray = true ; obsArray ( newValue ) ; insideObsArray = false ; } function patchArrayMethods ( array ) { const arrayMethods = deep ? deepArrayMethods : allArrayMethods ; arrayMethods . forEach ( ( fnName ) => defineProperty ( array , fnName , { value ( ) { if ( insideObsArray ) { return ArrayPrototype [ fnName ] . apply ( array , arguments ) ; } insideObsArray = true ; const result = obsArray [ fnName ] . apply ( obsArray , arguments ) ; insideObsArray = false ; return result ; } , } ) ) ; const observableArrayMethods = deep ? deepObservableArrayMethods : allObservableArrayMethods ; observableArrayMethods . forEach ( ( fnName ) => defineProperty ( array , fnName , { value ( ) { insideObsArray = true ; const result = obsArray [ fnName ] . apply ( obsArray , arguments ) ; insideObsArray = false ; return result ; } , } ) ) ; if ( deep ) { defineProperty ( array , "push" , { value ( ) { if ( insideObsArray ) { return ArrayPrototype . push . apply ( array , arguments ) ; } const args = arraySlice ( arguments ) ; for ( let i = 0 ; i < args . length ; ++ i ) { args [ i ] = prepareDeepValue ( args [ i ] , expose ) ; } insideObsArray = true ; const result = obsArray . push . apply ( obsArray , args ) ; insideObsArray = false ; return result ; } , } ) ; defineProperty ( array , "unshift" , { value ( ) { if ( insideObsArray ) { return ArrayPrototype . unshift . apply ( array , arguments ) ; } const args = arraySlice ( arguments ) ; for ( let i = 0 ; i < args . length ; ++ i ) { args [ i ] = prepareDeepValue ( args [ i ] , expose ) ; } insideObsArray = true ; const result = obsArray . unshift . apply ( obsArray , args ) ; insideObsArray = false ; return result ; } , } ) ; defineProperty ( array , "splice" , { value ( ) { if ( insideObsArray ) { return ArrayPrototype . splice . apply ( array , arguments ) ; } let result ; insideObsArray = true ; switch ( arguments . length ) { case 0 : case 1 : case 2 : { result = obsArray . splice . apply ( obsArray , arguments ) ; break ; } case 3 : { result = obsArray . splice ( arguments [ 0 ] , arguments [ 1 ] , prepareDeepValue ( arguments [ 2 ] , expose ) ) ; break ; } default : { const args = arraySlice ( arguments ) ; for ( let i = 2 ; i < args . length ; ++ i ) { args [ i ] = prepareDeepValue ( args [ i ] , expose ) ; } result = obsArray . splice . apply ( obsArray , arguments ) ; break ; } } insideObsArray = false ; return result ; } , } ) ; defineProperty ( array , "replace" , { value ( oldItem , newItem ) { insideObsArray = true ; const result = obsArray . replace ( oldItem , prepareDeepValue ( newItem , expose ) ) ; insideObsArray = false ; return result ; } , } ) ; defineProperty ( array , "mutate" , { value ( mutator ) { const nativeArray = obsArray . peek ( ) ; ( obsArray . valueWillMutate as Function ) ( ) ; mutator ( nativeArray ) ; for ( let i = 0 ; i < nativeArray . length ; ++ i ) { nativeArray [ i ] = prepareDeepValue ( nativeArray [ i ] , expose ) ; } ( obsArray . valueHasMutated as Function ) ( ) ; } , } ) ; defineProperty ( array , "set" , { value ( index , newItem ) { return obsArray . splice ( index , 1 , prepareDeepValue ( newItem , expose ) ) [ 0 ] ; } , } ) ; } else { defineProperty ( array , "mutate" , { value ( mutator ) { ( obsArray . valueWillMutate as Function ) ( ) ; mutator ( obsArray . peek ( ) ) ; ( obsArray . valueHasMutated as Function ) ( ) ; } , } ) ; defineProperty ( array , "set" , { value ( index , newItem ) { return obsArray . splice ( index , 1 , newItem ) [ 0 ] ; } , } ) ; } } }