UNPKG

mobx-location

Version:

minimal observable wrapper around browser location utilizing popstate event of HTML5 history api

127 lines (105 loc) 3.57 kB
import { set, observable, action, autorun, toJS, observe } from 'mobx' import queryString from 'query-string' import 'history-events' const propsToMirror = [ 'hash', 'host', 'hostname', 'href', 'origin', 'pathname', 'port', 'protocol', 'search' ] const { location } = window export default ({ hashHistory, arrayFormat = 'bracket' }) => { const createSnapshot = (previousQuery) => { const snapshot = propsToMirror.reduce((snapshot, prop) => { snapshot[prop] = location[prop] return snapshot }, {}) let q if (hashHistory) { q = queryString.parse(snapshot.hash.split('?')[1], { arrayFormat }) } else { q = queryString.parse(snapshot.search, { arrayFormat }) } snapshot.query = q || {} return snapshot } const firstSnapshot = createSnapshot() const locationObservable = observable(firstSnapshot) /** * executes each time a mobxLocation.query is mutated */ const propagateQueryToLocationSearch = () => { const queryInObservable = queryString.stringify( toJS(locationObservable.query), { encode: false, arrayFormat } ) // console.log('currentlyInObservable: ', currentlyInObservable) const { search, protocol, host, pathname, hash } = location let qs = search const hashParts = hash.split('?') if (hashHistory && hash.includes('?')) { qs = hashParts[1] } if (!qs && !queryInObservable) { return } if (decodeURI(qs) === queryInObservable) { return } if (qs !== queryInObservable) { let newUrl = protocol + '//' + host + pathname if (hashHistory) { const newHash = hashParts[0] + '?' + queryInObservable locationObservable.hash = newHash newUrl += newHash } else { const newSearch = '?' + queryInObservable + hash locationObservable.search = newSearch newUrl += newSearch } locationObservable.href = newUrl window.removeEventListener('changestate', snapshotAndSet) // console.log('newUrl: ', newUrl) newUrl = encodeURI(newUrl) history.replaceState(null, '', newUrl) window.addEventListener('changestate', snapshotAndSet) } } let unsubscribe = autorun(propagateQueryToLocationSearch) const snapshotAndSet = action('changestateHandler', (ev) => { const snapshot = createSnapshot(toJS(locationObservable.query)) const currentlyInObservable = toJS(locationObservable) //unfortunately we need to check that the new snapshot is different-for example when integrating with angularjs it happens that angular router is setting a URL again after we changed it via interacting with observable if ( snapshot.href !== currentlyInObservable && decodeURI(snapshot.href) !== locationObservable.href ) { set(locationObservable, snapshot) } }) observe(locationObservable, (change) => { const { name } = change if (name !== 'href') { return // we ignore these } if (decodeURI(location.href) === change.newValue) { return } if (location.href !== change.newValue) { const { search, protocol, host, pathname, hash } = locationObservable const newUrl = encodeURI( protocol + '//' + host + pathname + search + hash ) window.removeEventListener('changestate', snapshotAndSet) history.replaceState(null, '', newUrl) window.addEventListener('changestate', snapshotAndSet) } }) window.addEventListener('changestate', snapshotAndSet) return locationObservable }