/**
 * createApp at client
 */ import CreateHistoryMap , { useBasename , useBeforeUnload , useQueries , CreateHistory , ILWithBQ , History , BLWithBQ } from 'create-history' ; import defaultViewEngine from './viewEngine' ; import { createCache , createMap , ReqError } from '../share/util' ; import createMatcher from '../share/createMatcher' ; import defaultAppSettings from '../share/defaultSettings' ; import createController from './createController' ; import { Settings , Matcher , Context , ControllerConstructor , Cache , ControllerCacheFunc , HistoryLocation , ViewEngineRender , Listener , Loader , Route , Controller , WrapController } from '../share/type' ; import { CreateHistoryInCA , CreateApp , GetControllerByLocation , GetContainer , Render , InitController , CreateInitController , DestoryContainer , ClearContainer , Subscribe , Start , Stop , Publish , ClientController , ClientControllerConstructor } from './type' ; export const createHistory = ( settings ) => { let finalAppSettings = Object . assign ( { viewEngine : defaultViewEngine } , defaultAppSettings ) ; finalAppSettings = Object . assign ( finalAppSettings , settings ) ; let chInit = CreateHistoryMap [ finalAppSettings . type ] ; if ( finalAppSettings . basename ) { return useBeforeUnload ( useQueries ( useBasename ( chInit ) ) ) ( finalAppSettings ) ; } return useBeforeUnload ( useQueries ( chInit ) ) ( finalAppSettings ) ; } ; const createApp = ( settings ) => { let finalAppSettings = Object . assign ( { viewEngine : defaultViewEngine } , defaultAppSettings ) ; finalAppSettings = Object . assign ( finalAppSettings , settings ) ; let { routes , viewEngine , loader , context , container , cacheAmount , } = finalAppSettings ; context = { ... finalAppSettings . context , ... settings . context , } ; let history = createHistory ( finalAppSettings ) ; let matcher = createMatcher ( routes || [ ] ) ; let currentController = null ; let currentLocation = null ; let unlisten = null ; let finalContainer = null ; let cache = createCache ( cacheAmount ) ; const saveControllerToCache = ( controller ) => { cache . set ( controller . location . raw , controller ) ; } ; const getControllerFromCache = ( location ) => { return cache . get ( location . raw ) ; } ; const removeControllerFromCache = ( controller ) => { cache . remove ( controller . location . raw ) ; } ; const getContainer = ( ) => { if ( finalContainer ) { return finalContainer ; } if ( typeof container === 'string' ) { return finalContainer = document . querySelector ( container ) ; } else { return finalContainer = container ; } } ; const render = ( targetPath ) => { let location = typeof targetPath === 'string' ? history . createLocation ( targetPath ) : targetPath ; context . prevLocation = currentLocation ; let matches = matcher ( location . pathname ) ; if ( ! matches ) { throw new ReqError ( `Did not match any route with pathname:${ location . pathname }` , 404 ) ; } let { path , params , controller } = matches ; let finalLocation = Object . assign ( { pattern : path , params , raw : location . pathname + location . search } , location ) ; currentLocation = finalLocation ; let initController = createInitController ( finalLocation ) ; let iController = loader ( controller , finalLocation , context ) ; if ( Promise . resolve ( iController ) == iController ) { return ( < Promise < ControllerConstructor > > iController ) . then ( initController ) ; } else { return initController ( < ControllerConstructor > iController ) ; } } ; let controllers = createMap < ControllerConstructor , ClientControllerConstructor > ( ) ; const wrapController = ( IController ) => { if ( controllers . has ( IController ) ) { return controllers . get ( IController ) ; } class WrapperController extends IController { location : HistoryLocation ; context : Context ; history : History < BLWithBQ , ILWithBQ > ; matcher : Matcher ; loader : Loader ; routes : Route [ ] ; constructor ( location , context ) { super ( location , context ) ; this . location = location ; this . context = context ; this . history = history ; this . matcher = matcher ; this . loader = loader ; this . routes = routes || [ ] ; } public refreshView ( view = this . render ( ) ) { renderToContainer ( view , this ) ; } public getContainer ( ) { return getContainer ( ) ; } public clearContainer ( ) { clearContainer ( ) ; } public saveToCache ( ) { this . KeepAlive = true ; saveControllerToCache ( this ) ; } public removeFromCache ( ) { this . KeepAlive = false ; removeControllerFromCache ( this ) ; } public getAllCache ( ) { return cache . getAll ( ) ; } } controllers . set ( IController , WrapperController ) ; return WrapperController as ClientControllerConstructor ; } ; const createInitController = ( location ) => { const initController = ( iController ) => { if ( currentLocation !== location ) { return ; } destroyController ( ) ; let controller = currentController = getControllerFromCache ( location ) ; let element = null ; if ( ! ! controller ) { if ( controller . restore ) { element = controller . restore ( location , context ) ; } else { element = controller . init ( ) ; } controller . location = location ; controller . context = context ; } else { let FinalController = wrapController ( iController as ControllerConstructor ) ; controller = currentController = createController ( FinalController , location , context ) ; element = controller . init ( ) ; } if ( element == null ) { return null ; } if ( Promise . resolve ( element ) == element ) { return ( element as Promise < any > ) . then ( result => { if ( currentLocation !== location || result == null ) { return null ; } return renderToContainer ( result , controller ) ; } ) ; } return renderToContainer ( element , controller ) ; } ; return initController ; } ; const renderToContainer = ( element , controller ) => { if ( controller ) { saveControllerToCache ( controller ) ; } if ( ! viewEngine ) { return null ; } return viewEngine . render ( element , controller , getContainer ( ) ) ; } ; const clearContainer = ( ) => { if ( viewEngine && viewEngine . clear ) { let container = getContainer ( ) ; if ( container ) { return viewEngine . clear ( container ) ; } } } ; const destroyController = ( ) => { if ( currentController && ! currentController . KeepAlive ) { removeControllerFromCache ( currentController ) ; } if ( currentController && currentController . destroy ) { currentController . destroy ( ) ; currentController = null ; } } ; let listeners = [ ] ; const subscribe = ( listener ) => { let index = listeners . indexOf ( listener ) ; if ( index === - 1 ) { listeners . push ( listener ) ; } return ( ) => { let index = listeners . indexOf ( listener ) ; if ( index !== - 1 ) { listeners = listeners . filter ( fn => fn !== listener ) ; } } ; } ; const publish = ( location ) => { for ( let i = 0 , len = listeners . length ; i < len ; i ++ ) { listeners [ i ] ( location , history ) ; } } ; const start = ( callback , shouldRenderWithCurrentLocation ) => { let listener = location => { let result = render ( location ) ; if ( Promise . resolve ( result ) == result ) { result . then ( ( ) => { publish ( location ) ; } ) ; } else { publish ( location ) ; } } ; unlisten = history . listen ( listener ) ; let unsubscribe = null ; if ( typeof callback === 'function' ) { unsubscribe = subscribe ( callback ) ; } if ( shouldRenderWithCurrentLocation !== false ) { listener ( history . getCurrentLocation ( ) ) ; } return unsubscribe ; } ; const stop = ( ) => { if ( unlisten ) { unlisten ( ) ; destroyController ( ) ; currentController = null ; currentLocation = null ; unlisten = null ; finalContainer = null ; listeners = [ ] ; } } ; return { start , stop , render , history , subscribe , } ; } ; export default createApp ;