diff --git a/config/install/search_api_autocomplete.settings.yml b/config/install/search_api_autocomplete.settings.yml deleted file mode 100644 index a2b7cd1..0000000 --- a/config/install/search_api_autocomplete.settings.yml +++ /dev/null @@ -1,2 +0,0 @@ -delay: 0 -scripts: [] diff --git a/search_api_autocomplete.api.php b/search_api_autocomplete.api.php index 63e477b..0199796 100644 --- a/search_api_autocomplete.api.php +++ b/search_api_autocomplete.api.php @@ -423,10 +423,11 @@ function example_form_example_search_form_alter(array &$form, array &$form_state $search = \Drupal\search_api_autocomplete\Entity\SearchApiAutocompleteSearch::load($search_id); // Check whether autocompletion for the search is enabled. // (This is also checked automatically later, so could be skipped here.) - if (!empty($search->enabled)) { + if (!empty($search->status())) { // If it is, pass the textfield for the search keywords to the // alterElement() method of the search object. - $search->alterElement($form['keys']); + $autocomplete_form_utlity = new \Drupal\search_api_autocomplete\AutocompleteFormUtility(\Drupal::service('renderer')); + $autocomplete_form_utlity->alterElement($form['keys'], $search); } } diff --git a/search_api_autocomplete.search_api_page.inc b/search_api_autocomplete.search_api_page.inc index f19027a..68f9837 100644 --- a/search_api_autocomplete.search_api_page.inc +++ b/search_api_autocomplete.search_api_page.inc @@ -6,6 +6,7 @@ */ use Drupal\Core\Form\FormStateInterface; +use Drupal\search_api_autocomplete\AutocompleteFormUtility; use Drupal\search_api_autocomplete\Entity\SearchApiAutocompleteSearch; /** @@ -17,7 +18,9 @@ use Drupal\search_api_autocomplete\Entity\SearchApiAutocompleteSearch; function search_api_autocomplete_form_search_api_page_block_form_alter(array &$form, FormStateInterface $form_state) { $id = 'search_api_page_' . $form_state->getBuildInfo()['args'][0]['search_api_page']; $search = SearchApiAutocompleteSearch::load($id); + + $autocomplete_form_utlity = new AutocompleteFormUtility(\Drupal::service('renderer')); if ($search && $search->status()) { - $search->alterElement($form['keys']); + $autocomplete_form_utlity->alterElement($form['keys'], $search); } } diff --git a/search_api_autocomplete.search_api_views.inc b/search_api_autocomplete.search_api_views.inc index f4d1275..24ff108 100644 --- a/search_api_autocomplete.search_api_views.inc +++ b/search_api_autocomplete.search_api_views.inc @@ -7,6 +7,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\search_api\Entity\Index; +use Drupal\search_api_autocomplete\AutocompleteFormUtility; use Drupal\search_api_autocomplete\Controller\AutocompleteController; use Drupal\search_api_autocomplete\Entity\SearchApiAutocompleteSearch; @@ -24,7 +25,7 @@ function search_api_autocomplete_form_views_exposed_form_alter(array &$form, For } $search_id = 'search_api_views_' . $view->id(); $search = SearchApiAutocompleteSearch::load($search_id); - $controller = new AutocompleteController(); + $controller = new AutocompleteController(\Drupal::service('renderer')); if (!$search || !$controller->access($search, \Drupal::currentUser())->isAllowed()) { return; } @@ -36,6 +37,8 @@ function search_api_autocomplete_form_views_exposed_form_alter(array &$form, For } // Add the "Search: Fulltext search" filter as another text field. $fields[] = 'search_api_fulltext'; + + $autocomplete_form_utlity = new AutocompleteFormUtility(\Drupal::service('renderer')); \Drupal::moduleHandler()->alter('search_api_autocomplete_views_fulltext_fields', $fields, $search, $view); foreach ($view->filter as $filter_name => $filter) { if (in_array($filter->realField, $fields)) { @@ -48,7 +51,7 @@ function search_api_autocomplete_form_views_exposed_form_alter(array &$form, For else { $autocomplete_fields = [$filter->realField]; } - $search->alterElement($form[$key], $autocomplete_fields); + $autocomplete_form_utlity->alterElement($form[$key], $search, $autocomplete_fields); } } } diff --git a/src/AutocompleteFormUtility.php b/src/AutocompleteFormUtility.php new file mode 100644 index 0000000..a9ab0ef --- /dev/null +++ b/src/AutocompleteFormUtility.php @@ -0,0 +1,120 @@ +renderer = $renderer; + } + + /** + * Split a string with search keywords into two parts. + * + * The first part consists of all words the user has typed completely, the + * second one contains the beginning of the last, possibly incomplete word. + * + * @param string $keys + * The passed in keys. + * + * @return array + * An array with $keys split into exactly two parts, both of which may be + * empty. + */ + public static function splitKeys($keys) { + $keys = ltrim($keys); + // If there is whitespace or a quote on the right, all words have been + // completed. + if (rtrim($keys, " \"") != $keys) { + return [rtrim($keys, ' '), '']; + } + if (preg_match('/^(.*?)\s*"?([\S]*)$/', $keys, $m)) { + return [$m[1], $m[2]]; + } + return ['', $keys]; + } + + /** + * Helper method for altering a textfield form element to use autocompletion. + * + * @param array $element + * @param \Drupal\search_api_autocomplete\Controller\AutocompleteController $controller + * The autocomplete controller. + * @param array $fields + */ + public function alterElement(array &$element, SearchApiAutocompleteSearchInterface $search, array $fields = []) { + // @todo find a cleaner solution. + $controller = new AutocompleteController(\Drupal::service('renderer')); + if ($controller->access($search, \Drupal::currentUser())->isAllowed()) { + // Add option defaults (in case of updates from earlier versions). + $options = $search->getOptions() + [ + 'submit_button_selector' => ':submit', + 'autosubmit' => TRUE, + 'min_length' => 1, + ]; + + $fields_string = $fields ? implode(' ', $fields) : '-'; + + $autocomplete_route_name = 'search_api_autocomplete.autocomplete'; + $autocomplete_route_parameters = ['search_api_autocomplete_search' => $search->id(), 'fields' => $fields_string]; + + $js_settings = []; + if ($options['submit_button_selector'] != ':submit') { + $js_settings['selector'] = $options['submit_button_selector']; + } + if ($delay = $search->getOption('delay') !== NULL) { + $js_settings['delay'] = $delay; + } + + // Allow overriding of the default handler with a route. + if ($callback = $search->getOption('custom_autocomplete_url_callback')) { + $callback_options = $search->getOption('custom_autocomplete_url_options', []); + /** @var \Drupal\Core\Url $url */ + $url = call_user_func($callback, $this, $element, $callback_options); + $autocomplete_route_name = $url->getRouteName(); + $autocomplete_route_parameters = $url->getRouteParameters(); + $js_settings['custom_path'] = TRUE; + } + $element['#attached']['library'][] = 'search_api_autocomplete/search_api_autocomplete'; + if ($js_settings) { + $element['#attached']['drupalSettings'][] = [ + 'search_api_autocomplete' => [ + $search->id() => $js_settings, + ], + ]; + } + + $element['#autocomplete_route_name'] = $autocomplete_route_name; + $element['#autocomplete_route_parameters'] = $autocomplete_route_parameters; + $element += ['#attributes' => []]; + $element['#attributes'] += ['class' => []]; + if ($options['autosubmit']) { + $element['#attributes']['class'][] = 'auto_submit'; + } + $element['#attributes']['data-search-api-autocomplete-search'] = $search->id(); + if ($options['min_length'] > 1) { + $element['#attributes']['data-min-autocomplete-length'] = $options['min_length']; + } + } + } + +} diff --git a/src/Controller/AutocompleteController.php b/src/Controller/AutocompleteController.php index d3cf4a6..697b6a9 100644 --- a/src/Controller/AutocompleteController.php +++ b/src/Controller/AutocompleteController.php @@ -8,6 +8,7 @@ use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Render\RendererInterface; use Drupal\Core\Session\AccountInterface; use Drupal\search_api\SearchApiException; +use Drupal\search_api_autocomplete\AutocompleteFormUtility; use Drupal\search_api_autocomplete\SearchApiAutocompleteSearchInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\JsonResponse; @@ -60,10 +61,11 @@ class AutocompleteController extends ControllerBase implements ContainerInjectio */ public function autocomplete(SearchApiAutocompleteSearchInterface $search_api_autocomplete_search, $fields, Request $request) { $matches = []; + $autocomplete_utility = new AutocompleteFormUtility($this->renderer); try { if ($search_api_autocomplete_search->supportsAutocompletion()) { $keys = $request->query->get('q'); - list($complete, $incomplete) = $search_api_autocomplete_search->splitKeys($keys); + list($complete, $incomplete) = $autocomplete_utility->splitKeys($keys); $query = $search_api_autocomplete_search->getQuery($complete, $incomplete); if ($query) { // @todo Maybe make range configurable? diff --git a/src/Entity/SearchApiAutocompleteSearch.php b/src/Entity/SearchApiAutocompleteSearch.php index c9d95d6..1351de5 100644 --- a/src/Entity/SearchApiAutocompleteSearch.php +++ b/src/Entity/SearchApiAutocompleteSearch.php @@ -219,100 +219,6 @@ class SearchApiAutocompleteSearch extends ConfigEntityBase implements SearchApiA } /** - * Helper method for altering a textfield form element to use autocompletion. - * - * @param array $element - * @param array $fields - */ - public function alterElement(array &$element, array $fields = []) { - // @todo find a cleaner solution. - $controller = new AutocompleteController(\Drupal::service('renderer')); - $config = \Drupal::configFactory()->get('search_api_autocomplete.settings'); - if ($controller->access($this, \Drupal::currentUser())->isAllowed()) { - // Add option defaults (in case of updates from earlier versions). - $options = $this->options + [ - 'submit_button_selector' => ':submit', - 'autosubmit' => TRUE, - 'min_length' => 1, - ]; - - $fields_string = $fields ? implode(' ', $fields) : '-'; - - $autocomplete_route_name = 'search_api_autocomplete.autocomplete'; - $autocomplete_route_parameters = ['search_api_autocomplete_search' => $this->id(), 'fields' => $fields_string]; - - $js_settings = []; - if ($options['submit_button_selector'] != ':submit') { - $js_settings['selector'] = $options['submit_button_selector']; - } - if ($delay = $config->get('delay') !== NULL) { - $js_settings['delay'] = $delay; - } - - // Allow overriding of the default handler with a custom script. - // @todo implement that. - // $path_overrides = $config->get('scripts') ?: []; - // if (!empty($path_overrides[$this->id()])) { - // $autocomplete_path = NULL; - // $override = $path_overrides[$this->id()]; - // if (is_scalar($override)) { - // $autocomplete_path = url($override, ['absolute' => TRUE, 'query' => ['machine_name' => $this->id()]]); - // } - // elseif (!empty($override['#callback']) && is_callable($override['#callback'])) { - // $autocomplete_path = call_user_func($override['#callback'], $this, $element, $override); - // } - // if (!$autocomplete_path) { - // return; - // } - // $js_settings['custom_path'] = TRUE; - // }. - $element['#attached']['library'][] = 'search_api_autocomplete/search_api_autocomplete'; - if ($js_settings) { - $element['#attached']['drupalSettings'][] = [ - 'search_api_autocomplete' => [ - $this->id() => $js_settings, - ], - ]; - } - - $element['#autocomplete_route_name'] = $autocomplete_route_name; - $element['#autocomplete_route_parameters'] = $autocomplete_route_parameters; - $element += ['#attributes' => []]; - $element['#attributes'] += ['class' => []]; - if ($options['autosubmit']) { - $element['#attributes']['class'][] = 'auto_submit'; - } - $element['#attributes']['data-search-api-autocomplete-search'] = $this->id(); - if ($options['min_length'] > 1) { - $element['#attributes']['data-min-autocomplete-length'] = $options['min_length']; - } - } - } - - /** - * Split a string with search keywords into two parts. - * - * The first part consists of all words the user has typed completely, the - * second one contains the beginning of the last, possibly incomplete word. - * - * @return array - * An array with $keys split into exactly two parts, both of which may be - * empty. - */ - public function splitKeys($keys) { - $keys = ltrim($keys); - // If there is whitespace or a quote on the right, all words have been - // completed. - if (rtrim($keys, " \"") != $keys) { - return array(rtrim($keys, ' '), ''); - } - if (preg_match('/^(.*?)\s*"?([\S]*)$/', $keys, $m)) { - return array($m[1], $m[2]); - } - return array('', $keys); - } - - /** * Create the query that would be issued for this search for the complete keys. * * @param $complete diff --git a/src/Form/AutocompleteSearchEditForm.php b/src/Form/AutocompleteSearchEditForm.php index 4a096f8..b9f670f 100644 --- a/src/Form/AutocompleteSearchEditForm.php +++ b/src/Form/AutocompleteSearchEditForm.php @@ -186,6 +186,11 @@ class AutocompleteSearchEditForm extends EntityForm { '#description' => $this->t('Display the estimated number of result for each suggestion. This option might not have an effect for some servers or types of suggestion.'), '#default_value' => (bool) $search->getOption('results', FALSE), ]; + $form['options']['delay'] = [ + '#type' => 'textfield', + '#title' => $this->t('Delay in ms'), + '#default_value' => $search->getOption('delay'), + ]; $custom_form = empty($form['options']['custom']) ? [] : $form['options']['custom']; if ($type instanceof PluginFormInterface) { diff --git a/src/SearchApiAutocompleteSearchInterface.php b/src/SearchApiAutocompleteSearchInterface.php index f888268..220d926 100644 --- a/src/SearchApiAutocompleteSearchInterface.php +++ b/src/SearchApiAutocompleteSearchInterface.php @@ -29,8 +29,6 @@ interface SearchApiAutocompleteSearchInterface extends ConfigEntityInterface { public function server(); /** - * @fixme move out. - * * Determines whether autocompletion is currently supported for this search. * * @return bool @@ -40,32 +38,6 @@ interface SearchApiAutocompleteSearchInterface extends ConfigEntityInterface { public function supportsAutocompletion(); /** - * @fixme move out. - * - * Helper method for altering a textfield form element to use autocompletion. - * - * @param array $element - * @param array $fields - */ - public function alterElement(array &$element, array $fields = []); - - /** - * @fixme move out. - * - * Split a string with search keywords into two parts. - * - * The first part consists of all words the user has typed completely, the - * second one contains the beginning of the last, possibly incomplete word. - * - * @return array - * An array with $keys split into exactly two parts, both of which may be - * empty. - */ - public function splitKeys($keys); - - /** - * @fixme move out. - * * Create the query that would be issued for this search for the complete * keys. * diff --git a/tests/src/Unit/AutocompleteFormUtilityTest.php b/tests/src/Unit/AutocompleteFormUtilityTest.php new file mode 100644 index 0000000..c685db2 --- /dev/null +++ b/tests/src/Unit/AutocompleteFormUtilityTest.php @@ -0,0 +1,36 @@ +assertEquals($expected, AutocompleteFormUtility::splitKeys($keys)); + } + + /** + * Data provider for testSplitKeys(). + */ + public function providerTestSplitKeys() { + $data = []; + $data['simple-word'] = ['word', ['', 'word']]; + $data['simple-word-dash'] = ['word-dash', ['', 'word-dash']]; + $data['whitespace-right-side'] = ['word-dash ', ['word-dash', '']]; + $data['quote-word-start'] = ['"word" other', ['"word"', 'other']]; + $data['quote-word-end'] = ['word "other"', ['word "other"', '']]; + + return $data; + } + +}