PluginProbe
ElasticPress / 4.2.1
ElasticPress v4.2.1
5.3.5 5.3.4 3.6.5 3.6.6 4.0.0 4.0.1 4.1.0 4.2.0 4.2.1 4.2.2 4.3.0 4.3.1 4.4.0 4.4.1 4.5.0 4.5.1 4.5.2 4.6.0 4.6.1 4.7.0 4.7.1 4.7.2 5.0.0 5.0.1 5.0.2 All 108 releases
elasticpress / assets / js / instant-results / index.js

index.js in ElasticPress 4.2.1, at assets/js/instant-results/index.js

254 lines 5.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * WordPress dependencies.
3 */
4 import {
5 render,
6 useCallback,
7 useEffect,
8 useMemo,
9 useReducer,
10 useRef,
11 WPElement,
12 } from '@wordpress/element';
13 import { __ } from '@wordpress/i18n';
14
15 /**
16 * Internal dependencies.
17 */
18 import { argsSchema, paramPrefix } from './config';
19 import Context from './context';
20 import { getArgsFromUrlParams, getUrlParamsFromArgs, getPostTypesFromForm } from './functions';
21 import { useGetResults } from './hooks';
22 import { reducer, initialState } from './reducer';
23 import Layout from './components/layout';
24 import Modal from './components/common/modal';
25
26 /**
27 * component.
28 *
29 * @returns {WPElement} Element.
30 */
31 const App = () => {
32 const getResults = useGetResults();
33 const [state, dispatch] = useReducer(reducer, initialState);
34 const inputRef = useRef();
35 const modalRef = useRef();
36 const stateRef = useRef(state);
37
38 stateRef.current = state;
39
40 /**
41 * Close the modal.
42 */
43 const closeModal = useCallback(() => {
44 dispatch({ type: 'CLOSE_MODAL' });
45
46 if (inputRef.current) {
47 inputRef.current.focus();
48 }
49 }, []);
50
51 /**
52 * Start loading.
53 */
54 const startLoading = useCallback(() => {
55 dispatch({ type: 'START_LOADING' });
56 }, []);
57
58 /**
59 * Finish loading.
60 */
61 const finishLoading = useCallback(() => {
62 dispatch({ type: 'FINISH_LOADING' });
63 }, []);
64
65 /**
66 * Update search results.
67 *
68 * @param {object} response Search results.
69 */
70 const updateResults = useCallback((response) => {
71 if (response) {
72 dispatch({ type: 'NEW_SEARCH_RESULTS', payload: response });
73 }
74 }, []);
75
76 /**
77 * Perform a search.
78 */
79 const doSearch = useCallback(async () => {
80 const urlParams = getUrlParamsFromArgs(stateRef.current.args, argsSchema);
81
82 startLoading();
83
84 const response = await getResults(urlParams);
85
86 updateResults(response);
87
88 finishLoading();
89 }, [finishLoading, getResults, startLoading, updateResults]);
90
91 /**
92 * Push state to history.
93 */
94 const pushState = useCallback(() => {
95 const { history } = modalRef.current.ownerDocument.defaultView;
96 const { args, isOpen, isPoppingState } = stateRef.current;
97
98 if (isPoppingState) {
99 return;
100 }
101
102 const state = JSON.stringify({ ...args, isOpen });
103
104 if (history.state) {
105 const params = getUrlParamsFromArgs(args, argsSchema, paramPrefix);
106 const url = new URL(window.location.href);
107 const keys = Array.from(url.searchParams.keys());
108
109 for (const key of keys) {
110 if (key.startsWith(paramPrefix)) {
111 url.searchParams.delete(key);
112 }
113 }
114
115 if (isOpen) {
116 params.forEach((value, key) => {
117 url.searchParams.set(key, value);
118 });
119 }
120
121 history.pushState(state, document.title, url.toString());
122 } else {
123 history.replaceState(state, document.title, window.location.href);
124 }
125 }, []);
126
127 /**
128 * Handle escape key press.
129 *
130 * @param {Event} event Key down event.
131 */
132 const onEscape = useCallback(
133 (event) => {
134 if (event.key === 'Escape') {
135 closeModal();
136 }
137 },
138 [closeModal],
139 );
140
141 /**
142 * Handle popstate event.
143 *
144 * @param {Event} event popstate event.
145 */
146 const onPopState = useCallback((event) => {
147 if (event.state) {
148 dispatch({ type: 'POP_STATE', payload: JSON.parse(event.state) });
149 }
150 }, []);
151
152 /**
153 * Handle submitting the search form.
154 *
155 * @param {Event} event Input event.
156 */
157 const onSubmit = useCallback((event) => {
158 event.preventDefault();
159
160 inputRef.current = event.target.s;
161
162 const search = inputRef.current.value;
163 const post_type = getPostTypesFromForm(inputRef.current.form);
164
165 dispatch({ type: 'APPLY_ARGS', payload: { search, post_type } });
166 }, []);
167
168 /**
169 * Handle changes to search parameters.
170 */
171 const handleChanges = () => {
172 const { isOpen } = stateRef.current;
173
174 pushState();
175
176 if (!isOpen) {
177 return;
178 }
179
180 doSearch();
181 };
182
183 /**
184 * Bind events to outside elements.
185 *
186 * @returns {Function} A cleanup function that unbinds the events.
187 */
188 const handleEvents = () => {
189 const inputs = document.querySelectorAll('form input[type="search"');
190 const modal = modalRef.current;
191
192 inputs.forEach((input) => {
193 input.form.addEventListener('submit', onSubmit);
194 });
195
196 modal.ownerDocument.defaultView.addEventListener('popstate', onPopState);
197
198 return () => {
199 inputs.forEach((input) => {
200 input.form.removeEventListener('submit', onSubmit);
201 });
202
203 modal.ownerDocument.defaultView.removeEventListener('popstate', onPopState);
204 };
205 };
206
207 /**
208 * Open modal with pre-defined args if they are found in the URL.
209 */
210 const handleInit = () => {
211 const urlParams = new URLSearchParams(window.location.search);
212 const args = getArgsFromUrlParams(urlParams, argsSchema, paramPrefix, false);
213
214 if (Object.keys(args).length > 0) {
215 dispatch({ type: 'APPLY_ARGS', payload: args });
216 }
217 };
218
219 /**
220 * Effects.
221 */
222 useEffect(handleInit, []);
223 useEffect(handleEvents, [onEscape, onPopState, onSubmit]);
224 useEffect(handleChanges, [
225 doSearch,
226 pushState,
227 state.args,
228 state.args.orderby,
229 state.args.order,
230 state.args.offset,
231 state.args.search,
232 ]);
233
234 /**
235 * Create context.
236 */
237 const context = useMemo(() => ({ state, dispatch }), [state, dispatch]);
238
239 return (
240 <Context.Provider value={context}>
241 <Modal
242 aria-label={__('Search results', 'elasticpress')}
243 isOpen={state.isOpen}
244 onClose={closeModal}
245 ref={modalRef}
246 >
247 <Layout />
248 </Modal>
249 </Context.Provider>
250 );
251 };
252
253 render(<App />, document.getElementById('ep-instant-results'));
254