@elastic/search-ui
Advanced tools
Comparing version 1.18.0 to 1.18.1
@@ -645,2 +645,18 @@ "use strict"; | ||
})); | ||
describe("SearchDriver Routing Options", () => { | ||
it("should allow overrides to urlManager", () => { | ||
const initialState = {}; | ||
const routingOptions = { | ||
readUrl: jest.fn(() => { | ||
return "?q=override"; | ||
}), | ||
writeUrl: jest.fn() | ||
}; | ||
(0, helpers_1.setupDriver)({ | ||
initialState, | ||
routingOptions: routingOptions | ||
}); | ||
expect(MockedURLManager).toBeCalledWith(routingOptions); | ||
}); | ||
}); | ||
describe("SearchDriver hooks", () => { | ||
@@ -647,0 +663,0 @@ it("onSearch Hook", () => { |
@@ -7,4 +7,4 @@ "use strict"; | ||
const URLManager_1 = __importDefault(require("../URLManager")); | ||
function createManager() { | ||
const manager = new URLManager_1.default(); | ||
function createManager(options) { | ||
const manager = new URLManager_1.default(options); | ||
return manager; | ||
@@ -100,3 +100,3 @@ } | ||
manager.pushStateToURL(basicParameterState); | ||
const queryString = spy.mock.calls[0][0].search; | ||
const queryString = spy.mock.calls[0][0]; | ||
expect(queryString).toEqual("?q=node&size=n_20_n&filters%5B0%5D%5Bfield%5D=test&filters%5B0%5D%5Bvalues%5D%5B0%5D=value&filters%5B0%5D%5Btype%5D=all&filters%5B1%5D%5Bfield%5D=node&filters%5B1%5D%5Bvalues%5D%5B0%5D=value&filters%5B1%5D%5Btype%5D=all&sort-field=name&sort-direction=asc"); | ||
@@ -109,3 +109,3 @@ }); | ||
manager.pushStateToURL(parameterStateWithRangeFilters); | ||
const queryString = spy.mock.calls[0][0].search; | ||
const queryString = spy.mock.calls[0][0]; | ||
expect(queryString).toEqual("?filters%5B0%5D%5Bfield%5D=test&filters%5B0%5D%5Bvalues%5D%5B0%5D%5Bfrom%5D=n_12_n&filters%5B0%5D%5Bvalues%5D%5B0%5D%5Bname%5D=test&filters%5B0%5D%5Bvalues%5D%5B0%5D%5Bto%5D=n_4000_n&filters%5B0%5D%5Btype%5D=all"); | ||
@@ -119,3 +119,3 @@ }); | ||
manager.pushStateToURL(parameterStateWithSortList); | ||
const queryString = spy.mock.calls[0][0].search; | ||
const queryString = spy.mock.calls[0][0]; | ||
expect(queryString).toEqual("?size=n_20_n&sort%5B0%5D%5Bdirection%5D=asc&sort%5B0%5D%5Bfield%5D=name&sort%5B1%5D%5Bdirection%5D=desc&sort%5B1%5D%5Bfield%5D=title"); | ||
@@ -129,3 +129,3 @@ }); | ||
manager.pushStateToURL(basicParameterState, { replaceUrl: true }); | ||
const queryString = spy.mock.calls[0][0].search; | ||
const queryString = spy.mock.calls[0][0]; | ||
expect(queryString).toEqual("?q=node&size=n_20_n&filters%5B0%5D%5Bfield%5D=test&filters%5B0%5D%5Bvalues%5D%5B0%5D=value&filters%5B0%5D%5Btype%5D=all&filters%5B1%5D%5Bfield%5D=node&filters%5B1%5D%5Bvalues%5D%5B0%5D=value&filters%5B1%5D%5Btype%5D=all&sort-field=name&sort-direction=asc"); | ||
@@ -146,3 +146,3 @@ }); | ||
manager.pushStateToURL(state); | ||
return pushSpy.mock.calls[0][0].search; | ||
return pushSpy.mock.calls[0][0]; | ||
} | ||
@@ -222,1 +222,38 @@ function simulateBrowserHistoryEvent(newUrl) { | ||
}); | ||
describe("routing options overrides", () => { | ||
const routingOptions = { | ||
readUrl: jest.fn(() => { | ||
return "/search/tvs?query=samsung"; | ||
}), | ||
writeUrl: jest.fn(), | ||
stateToUrl: jest.fn((state) => { | ||
const categoryFilter = state.filters.find((filter) => filter.field === "category"); | ||
const category = categoryFilter ? categoryFilter.values[0] : "all"; | ||
return `/search/${category}?query=${state.searchTerm}`; | ||
}), | ||
urlToState: jest.fn((url) => { | ||
const match = url.match(/\/search\/(\w+)\?query=(\w+)/); | ||
if (!match) | ||
return {}; | ||
return { | ||
searchTerm: match[2], | ||
filters: [{ field: "category", values: [match[1]], type: "all" }] | ||
}; | ||
}) | ||
}; | ||
it("should write correct url", () => { | ||
const manager = createManager(routingOptions); | ||
manager.pushStateToURL({ | ||
searchTerm: "samsung", | ||
filters: [{ field: "category", values: ["gaming"], type: "all" }] | ||
}); | ||
expect(routingOptions.writeUrl).toHaveBeenCalledWith("/search/gaming?query=samsung", { replaceUrl: false }); | ||
}); | ||
it("should read correct url", () => { | ||
const manager = createManager(routingOptions); | ||
expect(manager.getStateFromURL()).toEqual({ | ||
searchTerm: "samsung", | ||
filters: [{ field: "category", values: ["tvs"], type: "all" }] | ||
}); | ||
}); | ||
}); |
@@ -1,2 +0,2 @@ | ||
import URLManager from "./URLManager"; | ||
import URLManager, { RoutingHandlerOptions } from "./URLManager"; | ||
import RequestSequencer from "./RequestSequencer"; | ||
@@ -25,2 +25,3 @@ import DebounceManager from "./DebounceManager"; | ||
trackUrlState?: boolean; | ||
routingOptions?: RoutingHandlerOptions; | ||
urlPushDebounceLength?: number; | ||
@@ -54,3 +55,3 @@ hasA11yNotifications?: boolean; | ||
apiConnector?: APIConnector; | ||
constructor({ apiConnector, autocompleteQuery, plugins, debug, initialState, onSearch, onAutocomplete, onResultClick, onAutocompleteResultClick, searchQuery, trackUrlState, urlPushDebounceLength, hasA11yNotifications, a11yNotificationMessages, alwaysSearchOnInitialLoad }: SearchDriverOptions); | ||
constructor({ apiConnector, autocompleteQuery, plugins, debug, initialState, onSearch, onAutocomplete, onResultClick, onAutocompleteResultClick, searchQuery, trackUrlState, routingOptions, urlPushDebounceLength, hasA11yNotifications, a11yNotificationMessages, alwaysSearchOnInitialLoad }: SearchDriverOptions); | ||
/** | ||
@@ -57,0 +58,0 @@ * This method is used to update state and trigger a new autocomplete search. |
@@ -100,3 +100,3 @@ "use strict"; | ||
class SearchDriver { | ||
constructor({ apiConnector, autocompleteQuery = {}, plugins = [], debug, initialState, onSearch, onAutocomplete, onResultClick, onAutocompleteResultClick, searchQuery = {}, trackUrlState = true, urlPushDebounceLength = 500, hasA11yNotifications = false, a11yNotificationMessages = {}, alwaysSearchOnInitialLoad = false }) { | ||
constructor({ apiConnector, autocompleteQuery = {}, plugins = [], debug, initialState, onSearch, onAutocomplete, onResultClick, onAutocompleteResultClick, searchQuery = {}, trackUrlState = true, routingOptions = {}, urlPushDebounceLength = 500, hasA11yNotifications = false, a11yNotificationMessages = {}, alwaysSearchOnInitialLoad = false }) { | ||
this.state = exports.DEFAULT_STATE; | ||
@@ -285,3 +285,3 @@ /** | ||
if (trackUrlState) { | ||
this.URLManager = new URLManager_1.default(); | ||
this.URLManager = new URLManager_1.default(routingOptions); | ||
urlState = this.URLManager.getStateFromURL(); | ||
@@ -288,0 +288,0 @@ this.URLManager.onURLStateChange((urlState) => { |
@@ -20,2 +20,13 @@ import { History } from "history"; | ||
*/ | ||
interface RoutingHandler { | ||
readUrl: () => string; | ||
writeUrl: (url: string, { replaceUrl }: { | ||
replaceUrl: boolean; | ||
}) => void; | ||
urlToState: (url: string) => RequestState; | ||
stateToUrl: (state: RequestState) => string; | ||
routeChangeHandler: (handler: (url?: string) => void) => () => void; | ||
} | ||
export declare type RoutingHandlerOptions = Partial<RoutingHandler>; | ||
declare type RoutingChangeCallback = (state: RequestState) => void; | ||
export default class URLManager { | ||
@@ -25,3 +36,11 @@ history: History; | ||
unlisten?: () => void; | ||
constructor(); | ||
overrides: any; | ||
routingOptions: RoutingHandler; | ||
constructor(routingOptions?: RoutingHandlerOptions); | ||
readUrl(): string; | ||
writeUrl(url: string, { replaceUrl }?: { | ||
replaceUrl?: boolean; | ||
}): void; | ||
urlToState(url: string): RequestState; | ||
stateToUrl(state: RequestState): string; | ||
/** | ||
@@ -52,4 +71,6 @@ * Parse the current URL into application state | ||
*/ | ||
onURLStateChange(callback: (state: RequestState) => void): void; | ||
onURLStateChange(callback: RoutingChangeCallback): void; | ||
routeChangeHandler(callback: any): import("history").UnregisterCallback; | ||
tearDown(): void; | ||
} | ||
export {}; |
@@ -84,21 +84,11 @@ "use strict"; | ||
} | ||
/** | ||
* The URL Manager is responsible for synchronizing state between | ||
* SearchDriver and the URL. There are 3 main cases we handle when | ||
* synchronizing: | ||
* | ||
* 1. When the app loads, SearchDriver will need to | ||
* read the current state from the URL, in order to perform the search | ||
* expressed by the query string. `getStateFromURL` is used for this case. | ||
* | ||
* 2. When the URL changes as a result of `pushState` or `replaceState`, | ||
* SearchDriver will need to be notified and given the updated state, so that | ||
* it can re-run the current search. `onURLStateChange` is used for this case. | ||
* | ||
* 3. When state changes internally in the SearchDriver, as a result of an | ||
* Action, it will need to notify the URLManager of the change. `pushStateToURL` | ||
* is used for this case. | ||
*/ | ||
class URLManager { | ||
constructor() { | ||
constructor(routingOptions = {}) { | ||
this.routingOptions = { | ||
readUrl: routingOptions.readUrl || this.readUrl.bind(this), | ||
writeUrl: routingOptions.writeUrl || this.writeUrl.bind(this), | ||
urlToState: routingOptions.urlToState || this.urlToState.bind(this), | ||
stateToUrl: routingOptions.stateToUrl || this.stateToUrl.bind(this), | ||
routeChangeHandler: routingOptions.routeChangeHandler || this.routeChangeHandler.bind(this) | ||
}; | ||
this.history = | ||
@@ -108,2 +98,29 @@ typeof window !== "undefined" ? (0, history_1.createBrowserHistory)() : (0, history_1.createMemoryHistory)(); | ||
} | ||
/* | ||
* These functions are used to read and write the URL | ||
* Its designed to be overriden by the developer for their own 3rd party routing needs. | ||
* For example developers override this function to use next.js | ||
* | ||
**/ | ||
readUrl() { | ||
return this.history ? this.history.location.search : ""; | ||
} | ||
writeUrl(url, { replaceUrl = false } = {}) { | ||
const navigationFunction = replaceUrl | ||
? this.history.replace | ||
: this.history.push; | ||
navigationFunction(`?${url}`); | ||
} | ||
/* | ||
* This function is used to convert a URL into a state object and vice versa | ||
* the state is stored as a search string in the URL. | ||
* Developers own implementations of this function should be able to handle full urls | ||
* and not just the search string. | ||
**/ | ||
urlToState(url) { | ||
return paramsToState(queryString_1.default.parse(url)); | ||
} | ||
stateToUrl(state) { | ||
return `${stateToQueryString(state)}`; | ||
} | ||
/** | ||
@@ -115,4 +132,3 @@ * Parse the current URL into application state | ||
getStateFromURL() { | ||
const searchString = this.history ? this.history.location.search : ""; | ||
return paramsToState(queryString_1.default.parse(searchString)); | ||
return this.routingOptions.urlToState(this.routingOptions.readUrl()); | ||
} | ||
@@ -128,10 +144,5 @@ /** | ||
pushStateToURL(state, { replaceUrl = false } = {}) { | ||
const searchString = stateToQueryString(state); | ||
this.lastPushSearchString = searchString; | ||
const navigationFunction = replaceUrl | ||
? this.history.replace | ||
: this.history.push; | ||
navigationFunction({ | ||
search: `?${searchString}` | ||
}); | ||
const url = this.routingOptions.stateToUrl(state); | ||
this.lastPushSearchString = url; | ||
this.routingOptions.writeUrl(url, { replaceUrl }); | ||
} | ||
@@ -147,6 +158,4 @@ /** | ||
onURLStateChange(callback) { | ||
this.unlisten = this.history.listen((location) => { | ||
// If this URL is updated as a result of a pushState request, we don't | ||
// want to notify that the URL changed. | ||
if (`?${this.lastPushSearchString}` === location.search) | ||
const handler = (url) => { | ||
if (`?${this.lastPushSearchString}` === url) | ||
return; | ||
@@ -156,5 +165,12 @@ // Once we've decided to return based on lastPushSearchString, reset | ||
this.lastPushSearchString = ""; | ||
callback(paramsToState(queryString_1.default.parse(location.search))); | ||
}); | ||
callback(this.routingOptions.urlToState(url)); | ||
}; | ||
this.unlisten = this.routingOptions.routeChangeHandler(handler.bind(this)); | ||
} | ||
routeChangeHandler(callback) { | ||
const handler = (location) => { | ||
callback(location.search); | ||
}; | ||
return this.history.listen(handler); | ||
} | ||
tearDown() { | ||
@@ -161,0 +177,0 @@ this.unlisten(); |
@@ -621,2 +621,18 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
})); | ||
describe("SearchDriver Routing Options", () => { | ||
it("should allow overrides to urlManager", () => { | ||
const initialState = {}; | ||
const routingOptions = { | ||
readUrl: jest.fn(() => { | ||
return "?q=override"; | ||
}), | ||
writeUrl: jest.fn() | ||
}; | ||
setupDriver({ | ||
initialState, | ||
routingOptions: routingOptions | ||
}); | ||
expect(MockedURLManager).toBeCalledWith(routingOptions); | ||
}); | ||
}); | ||
describe("SearchDriver hooks", () => { | ||
@@ -623,0 +639,0 @@ it("onSearch Hook", () => { |
import URLManager from "../URLManager"; | ||
function createManager() { | ||
const manager = new URLManager(); | ||
function createManager(options) { | ||
const manager = new URLManager(options); | ||
return manager; | ||
@@ -94,3 +94,3 @@ } | ||
manager.pushStateToURL(basicParameterState); | ||
const queryString = spy.mock.calls[0][0].search; | ||
const queryString = spy.mock.calls[0][0]; | ||
expect(queryString).toEqual("?q=node&size=n_20_n&filters%5B0%5D%5Bfield%5D=test&filters%5B0%5D%5Bvalues%5D%5B0%5D=value&filters%5B0%5D%5Btype%5D=all&filters%5B1%5D%5Bfield%5D=node&filters%5B1%5D%5Bvalues%5D%5B0%5D=value&filters%5B1%5D%5Btype%5D=all&sort-field=name&sort-direction=asc"); | ||
@@ -103,3 +103,3 @@ }); | ||
manager.pushStateToURL(parameterStateWithRangeFilters); | ||
const queryString = spy.mock.calls[0][0].search; | ||
const queryString = spy.mock.calls[0][0]; | ||
expect(queryString).toEqual("?filters%5B0%5D%5Bfield%5D=test&filters%5B0%5D%5Bvalues%5D%5B0%5D%5Bfrom%5D=n_12_n&filters%5B0%5D%5Bvalues%5D%5B0%5D%5Bname%5D=test&filters%5B0%5D%5Bvalues%5D%5B0%5D%5Bto%5D=n_4000_n&filters%5B0%5D%5Btype%5D=all"); | ||
@@ -113,3 +113,3 @@ }); | ||
manager.pushStateToURL(parameterStateWithSortList); | ||
const queryString = spy.mock.calls[0][0].search; | ||
const queryString = spy.mock.calls[0][0]; | ||
expect(queryString).toEqual("?size=n_20_n&sort%5B0%5D%5Bdirection%5D=asc&sort%5B0%5D%5Bfield%5D=name&sort%5B1%5D%5Bdirection%5D=desc&sort%5B1%5D%5Bfield%5D=title"); | ||
@@ -123,3 +123,3 @@ }); | ||
manager.pushStateToURL(basicParameterState, { replaceUrl: true }); | ||
const queryString = spy.mock.calls[0][0].search; | ||
const queryString = spy.mock.calls[0][0]; | ||
expect(queryString).toEqual("?q=node&size=n_20_n&filters%5B0%5D%5Bfield%5D=test&filters%5B0%5D%5Bvalues%5D%5B0%5D=value&filters%5B0%5D%5Btype%5D=all&filters%5B1%5D%5Bfield%5D=node&filters%5B1%5D%5Bvalues%5D%5B0%5D=value&filters%5B1%5D%5Btype%5D=all&sort-field=name&sort-direction=asc"); | ||
@@ -140,3 +140,3 @@ }); | ||
manager.pushStateToURL(state); | ||
return pushSpy.mock.calls[0][0].search; | ||
return pushSpy.mock.calls[0][0]; | ||
} | ||
@@ -216,1 +216,38 @@ function simulateBrowserHistoryEvent(newUrl) { | ||
}); | ||
describe("routing options overrides", () => { | ||
const routingOptions = { | ||
readUrl: jest.fn(() => { | ||
return "/search/tvs?query=samsung"; | ||
}), | ||
writeUrl: jest.fn(), | ||
stateToUrl: jest.fn((state) => { | ||
const categoryFilter = state.filters.find((filter) => filter.field === "category"); | ||
const category = categoryFilter ? categoryFilter.values[0] : "all"; | ||
return `/search/${category}?query=${state.searchTerm}`; | ||
}), | ||
urlToState: jest.fn((url) => { | ||
const match = url.match(/\/search\/(\w+)\?query=(\w+)/); | ||
if (!match) | ||
return {}; | ||
return { | ||
searchTerm: match[2], | ||
filters: [{ field: "category", values: [match[1]], type: "all" }] | ||
}; | ||
}) | ||
}; | ||
it("should write correct url", () => { | ||
const manager = createManager(routingOptions); | ||
manager.pushStateToURL({ | ||
searchTerm: "samsung", | ||
filters: [{ field: "category", values: ["gaming"], type: "all" }] | ||
}); | ||
expect(routingOptions.writeUrl).toHaveBeenCalledWith("/search/gaming?query=samsung", { replaceUrl: false }); | ||
}); | ||
it("should read correct url", () => { | ||
const manager = createManager(routingOptions); | ||
expect(manager.getStateFromURL()).toEqual({ | ||
searchTerm: "samsung", | ||
filters: [{ field: "category", values: ["tvs"], type: "all" }] | ||
}); | ||
}); | ||
}); |
@@ -1,2 +0,2 @@ | ||
import URLManager from "./URLManager"; | ||
import URLManager, { RoutingHandlerOptions } from "./URLManager"; | ||
import RequestSequencer from "./RequestSequencer"; | ||
@@ -25,2 +25,3 @@ import DebounceManager from "./DebounceManager"; | ||
trackUrlState?: boolean; | ||
routingOptions?: RoutingHandlerOptions; | ||
urlPushDebounceLength?: number; | ||
@@ -54,3 +55,3 @@ hasA11yNotifications?: boolean; | ||
apiConnector?: APIConnector; | ||
constructor({ apiConnector, autocompleteQuery, plugins, debug, initialState, onSearch, onAutocomplete, onResultClick, onAutocompleteResultClick, searchQuery, trackUrlState, urlPushDebounceLength, hasA11yNotifications, a11yNotificationMessages, alwaysSearchOnInitialLoad }: SearchDriverOptions); | ||
constructor({ apiConnector, autocompleteQuery, plugins, debug, initialState, onSearch, onAutocomplete, onResultClick, onAutocompleteResultClick, searchQuery, trackUrlState, routingOptions, urlPushDebounceLength, hasA11yNotifications, a11yNotificationMessages, alwaysSearchOnInitialLoad }: SearchDriverOptions); | ||
/** | ||
@@ -57,0 +58,0 @@ * This method is used to update state and trigger a new autocomplete search. |
@@ -75,3 +75,3 @@ var __rest = (this && this.__rest) || function (s, e) { | ||
class SearchDriver { | ||
constructor({ apiConnector, autocompleteQuery = {}, plugins = [], debug, initialState, onSearch, onAutocomplete, onResultClick, onAutocompleteResultClick, searchQuery = {}, trackUrlState = true, urlPushDebounceLength = 500, hasA11yNotifications = false, a11yNotificationMessages = {}, alwaysSearchOnInitialLoad = false }) { | ||
constructor({ apiConnector, autocompleteQuery = {}, plugins = [], debug, initialState, onSearch, onAutocomplete, onResultClick, onAutocompleteResultClick, searchQuery = {}, trackUrlState = true, routingOptions = {}, urlPushDebounceLength = 500, hasA11yNotifications = false, a11yNotificationMessages = {}, alwaysSearchOnInitialLoad = false }) { | ||
this.state = DEFAULT_STATE; | ||
@@ -260,3 +260,3 @@ /** | ||
if (trackUrlState) { | ||
this.URLManager = new URLManager(); | ||
this.URLManager = new URLManager(routingOptions); | ||
urlState = this.URLManager.getStateFromURL(); | ||
@@ -263,0 +263,0 @@ this.URLManager.onURLStateChange((urlState) => { |
@@ -20,2 +20,13 @@ import { History } from "history"; | ||
*/ | ||
interface RoutingHandler { | ||
readUrl: () => string; | ||
writeUrl: (url: string, { replaceUrl }: { | ||
replaceUrl: boolean; | ||
}) => void; | ||
urlToState: (url: string) => RequestState; | ||
stateToUrl: (state: RequestState) => string; | ||
routeChangeHandler: (handler: (url?: string) => void) => () => void; | ||
} | ||
export declare type RoutingHandlerOptions = Partial<RoutingHandler>; | ||
declare type RoutingChangeCallback = (state: RequestState) => void; | ||
export default class URLManager { | ||
@@ -25,3 +36,11 @@ history: History; | ||
unlisten?: () => void; | ||
constructor(); | ||
overrides: any; | ||
routingOptions: RoutingHandler; | ||
constructor(routingOptions?: RoutingHandlerOptions); | ||
readUrl(): string; | ||
writeUrl(url: string, { replaceUrl }?: { | ||
replaceUrl?: boolean; | ||
}): void; | ||
urlToState(url: string): RequestState; | ||
stateToUrl(state: RequestState): string; | ||
/** | ||
@@ -52,4 +71,6 @@ * Parse the current URL into application state | ||
*/ | ||
onURLStateChange(callback: (state: RequestState) => void): void; | ||
onURLStateChange(callback: RoutingChangeCallback): void; | ||
routeChangeHandler(callback: any): import("history").UnregisterCallback; | ||
tearDown(): void; | ||
} | ||
export {}; |
@@ -79,21 +79,11 @@ import { createBrowserHistory as createHistory, createMemoryHistory } from "history"; | ||
} | ||
/** | ||
* The URL Manager is responsible for synchronizing state between | ||
* SearchDriver and the URL. There are 3 main cases we handle when | ||
* synchronizing: | ||
* | ||
* 1. When the app loads, SearchDriver will need to | ||
* read the current state from the URL, in order to perform the search | ||
* expressed by the query string. `getStateFromURL` is used for this case. | ||
* | ||
* 2. When the URL changes as a result of `pushState` or `replaceState`, | ||
* SearchDriver will need to be notified and given the updated state, so that | ||
* it can re-run the current search. `onURLStateChange` is used for this case. | ||
* | ||
* 3. When state changes internally in the SearchDriver, as a result of an | ||
* Action, it will need to notify the URLManager of the change. `pushStateToURL` | ||
* is used for this case. | ||
*/ | ||
export default class URLManager { | ||
constructor() { | ||
constructor(routingOptions = {}) { | ||
this.routingOptions = { | ||
readUrl: routingOptions.readUrl || this.readUrl.bind(this), | ||
writeUrl: routingOptions.writeUrl || this.writeUrl.bind(this), | ||
urlToState: routingOptions.urlToState || this.urlToState.bind(this), | ||
stateToUrl: routingOptions.stateToUrl || this.stateToUrl.bind(this), | ||
routeChangeHandler: routingOptions.routeChangeHandler || this.routeChangeHandler.bind(this) | ||
}; | ||
this.history = | ||
@@ -103,2 +93,29 @@ typeof window !== "undefined" ? createHistory() : createMemoryHistory(); | ||
} | ||
/* | ||
* These functions are used to read and write the URL | ||
* Its designed to be overriden by the developer for their own 3rd party routing needs. | ||
* For example developers override this function to use next.js | ||
* | ||
**/ | ||
readUrl() { | ||
return this.history ? this.history.location.search : ""; | ||
} | ||
writeUrl(url, { replaceUrl = false } = {}) { | ||
const navigationFunction = replaceUrl | ||
? this.history.replace | ||
: this.history.push; | ||
navigationFunction(`?${url}`); | ||
} | ||
/* | ||
* This function is used to convert a URL into a state object and vice versa | ||
* the state is stored as a search string in the URL. | ||
* Developers own implementations of this function should be able to handle full urls | ||
* and not just the search string. | ||
**/ | ||
urlToState(url) { | ||
return paramsToState(queryString.parse(url)); | ||
} | ||
stateToUrl(state) { | ||
return `${stateToQueryString(state)}`; | ||
} | ||
/** | ||
@@ -110,4 +127,3 @@ * Parse the current URL into application state | ||
getStateFromURL() { | ||
const searchString = this.history ? this.history.location.search : ""; | ||
return paramsToState(queryString.parse(searchString)); | ||
return this.routingOptions.urlToState(this.routingOptions.readUrl()); | ||
} | ||
@@ -123,10 +139,5 @@ /** | ||
pushStateToURL(state, { replaceUrl = false } = {}) { | ||
const searchString = stateToQueryString(state); | ||
this.lastPushSearchString = searchString; | ||
const navigationFunction = replaceUrl | ||
? this.history.replace | ||
: this.history.push; | ||
navigationFunction({ | ||
search: `?${searchString}` | ||
}); | ||
const url = this.routingOptions.stateToUrl(state); | ||
this.lastPushSearchString = url; | ||
this.routingOptions.writeUrl(url, { replaceUrl }); | ||
} | ||
@@ -142,6 +153,4 @@ /** | ||
onURLStateChange(callback) { | ||
this.unlisten = this.history.listen((location) => { | ||
// If this URL is updated as a result of a pushState request, we don't | ||
// want to notify that the URL changed. | ||
if (`?${this.lastPushSearchString}` === location.search) | ||
const handler = (url) => { | ||
if (`?${this.lastPushSearchString}` === url) | ||
return; | ||
@@ -151,5 +160,12 @@ // Once we've decided to return based on lastPushSearchString, reset | ||
this.lastPushSearchString = ""; | ||
callback(paramsToState(queryString.parse(location.search))); | ||
}); | ||
callback(this.routingOptions.urlToState(url)); | ||
}; | ||
this.unlisten = this.routingOptions.routeChangeHandler(handler.bind(this)); | ||
} | ||
routeChangeHandler(callback) { | ||
const handler = (location) => { | ||
callback(location.search); | ||
}; | ||
return this.history.listen(handler); | ||
} | ||
tearDown() { | ||
@@ -156,0 +172,0 @@ this.unlisten(); |
{ | ||
"name": "@elastic/search-ui", | ||
"version": "1.18.0", | ||
"version": "1.18.1", | ||
"description": "A Headless Search UI library", | ||
@@ -48,3 +48,3 @@ "license": "Apache-2.0", | ||
}, | ||
"gitHead": "09e05a411ce80922d81a2fbddd66d5869104ce7c" | ||
"gitHead": "263b5630928ce5573b0d9fb3705b75ff74bd4b8b" | ||
} |
437090
10539