';
+
+ // Cache selectors.
+ var $input = $(this);
+ var $form = $('#federated-search-page-block-form');
+ // Set up input with attributes, suggestions scaffolding.
+ $input.attr("role","combobox")
+ .attr("aria-owns","res")
+ .attr("aria-autocomplete","list")
+ .attr("aria-expanded","false");
+ $(suggestionsContainerScaffoldingMarkup).insertAfter($input);
+ // Cache inserted selectors.
+ var $results = $('#res');
+ var $autocompleteContainer = $('.search-autocomplete-container');
+ var $closeButton = $('.search-autocomplete-container__close-button');
+
+ // Initiate helper vars.
+ var current;
+ var counter = 1;
+ var keys = {
+ ESC: 27,
+ TAB: 9,
+ RETURN: 13,
+ UP: 38,
+ DOWN: 40
+ };
+
+ // Determine param values for any set default filters/facets.
+ var defaultParams = '';
+ $('input[type="hidden"]', $form).each(function(index, input) {
+ defaultParams += '&' + $(input).attr('name') + '=' + encodeURI($(input).val());
+ });
+ var urlWithDefaultParams = options.url + defaultParams;
+
+
+ // Bind events to input.
+ $input.on("input", function(event) {
+ doSearch(options.suggestionRows);
+ });
+
+ $input.on("keydown", function(event) {
+ doKeypress(keys, event);
+ });
+
+ // Define event handlers.
+ function doSearch(suggestionRows) {
+ $input.removeAttr("aria-activedescendant");
+ var value = $input.val();
+ // Remove spaces on either end of the value.
+ var trimmed = value.trim();
+ // Default to the trimmed value.
+ var query = trimmed;
+ // If the current value has more than the configured number of characters.
+ if (query.length > options.numChars) {
+ // Append wildcard to the query if configured to do so.
+ if (options.appendWildcard) {
+ // One method of supporting search-as-you-type is to append a wildcard '*'
+ // to match zero or more additional characters at the end of the users search term.
+ // @see: https://lucene.apache.org/solr/guide/6_6/the-standard-query-parser.html#TheStandardQueryParser-WildcardSearches
+ // @see: https://opensourceconnections.com/blog/2013/06/07/search-as-you-type-with-solr/
+ // Split into word chunks.
+ const words = trimmed.split(" ");
+ // If there are multiple chunks, join them with "+", repeat the last word + append "*".
+ if (words.length > 1) {
+ query = words.join("+") + words.pop() + '*';
+ }
+ else {
+ // If there is only 1 word, repeat it an append "*".
+ query = words + '+' + words + '*';
+ }
+ }
+
+ // Replace the placeholder with the query value.
+ var pattern = new RegExp(/(\[val\])/, "gi");
+ var url = urlWithDefaultParams.replace(pattern, query);
+
+ // Set up basic auth if we need it.
+ var xhrFields = {};
+ var headers = {};
+ if (options.userpass) {
+ xhrFields = {
+ withCredentials: true
+ };
+ headers = {
+ 'Authorization': 'Basic ' + options.userpass
+ };
+ }
+
+ // Make the ajax request
+ $.ajax({
+ xhrFields: xhrFields,
+ headers: headers,
+ url: url,
+ dataType: 'json',
+ })
+ // Currently we only support the response structure from Solr:
+ // {
+ // response: {
+ // docs: [
+ // {
+ // ss_federated_title: ,
+ // ss_url: ,
+ // }
+ // ]
+ // }
+ // }
+
+ // @todo provide hook for transform function to be passed in
+ // via Drupal.settings then all it here.
+ .done(function( results ) {
+ if (results.response.docs.length >= 1) {
+ // Remove all suggestions
+ $('.autocomplete-suggestion').remove();
+ $autocompleteContainer.removeClass('visually-hidden');
+ $("#search-autocomplete").append('');
+ $input.attr("aria-expanded", "true");
+ counter = 1;
+
+ // Bind click event for close button
+ $closeButton.on("click", function (event) {
+ event.preventDefault();
+ event.stopPropagation();
+ $input.removeAttr("aria-activedescendant");
+ // Remove all suggestions
+ $('.autocomplete-suggestion').remove();
+ $autocompleteContainer.addClass('visually-hidden');
+ $input.attr("aria-expanded", "false");
+ $input.focus();
+ });
+
+ // Get first [suggestionRows] results
+ var limitedResults = results.response.docs.slice(0, suggestionRows);
+ limitedResults.forEach(function(item) {
+ // Highlight query chars in returned title
+ var pattern = new RegExp(trimmed, "gi");
+ var highlighted = item.ss_federated_title.replace(pattern, function(string) {
+ return "" + string + ""
+ });
+
+ //Add results to the list
+ $results.append("
");
+ counter = counter + 1;
+ });
+
+ // On link click, emit an event whose data can be used to write to analytics, etc.
+ $('.autocomplete-suggestion__link').on('click', function (e) {
+ $(document).trigger("SearchApiFederatedSolr::block::autocomplete::selection", [{
+ referrer: $(location).attr('href'),
+ target: $(this).attr('href'),
+ term: $input.val()
+ }]);
+ });
+
+ // Announce the number of suggestions.
+ var number = $results.children('[role="option"]').length;
+ if (number >= 1) {
+ Drupal.announce(Drupal.t(number + " suggestions displayed. To navigate use up and down arrow keys."));
+ }
+ } else {
+ // No results, remove suggestions and hide container
+ $('.autocomplete-suggestion').remove();
+ $autocompleteContainer.addClass('visually-hidden');
+ $input.attr("aria-expanded","false");
+ }
+ });
+ }
+ else {
+ // Remove suggestions and hide container
+ $('.autocomplete-suggestion').remove();
+ $autocompleteContainer.addClass('visually-hidden');
+ $input.attr("aria-expanded","false");
+ }
+ }
+
+ function doKeypress(keys, event) {
+ var $suggestions = $('.autocomplete-suggestion');
+ var highlighted = false;
+ highlighted = $results.children('div').hasClass('highlight');
+
+ switch (event.which) {
+ case keys.ESC:
+ event.preventDefault();
+ event.stopPropagation();
+ $input.removeAttr("aria-activedescendant");
+ $suggestions.remove();
+ $autocompleteContainer.addClass('visually-hidden');
+ $input.attr("aria-expanded","false");
+ break;
+
+ case keys.TAB:
+ $input.removeAttr("aria-activedescendant");
+ $suggestions.remove();
+ $autocompleteContainer.addClass('visually-hidden');
+ $input.attr("aria-expanded","false");
+ break;
+
+ case keys.RETURN:
+ if (highlighted) {
+ event.preventDefault();
+ event.stopPropagation();
+ return selectOption(highlighted, $('.highlight').find('a').attr('href'));
+ }
+ else {
+ $form.submit();
+ return false;
+ }
+ break;
+
+ case keys.UP:
+ event.preventDefault();
+ event.stopPropagation();
+ return moveUp(highlighted);
+ break;
+
+ case keys.DOWN:
+ event.preventDefault();
+ event.stopPropagation();
+ return moveDown(highlighted);
+ break;
+
+ default:
+ return;
+ }
+ }
+
+ function moveUp(highlighted) {
+ $input.removeAttr("aria-activedescendant");
+
+ // if highlighted exists and if the highlighted item is not the first option
+ if (highlighted && !$results.children().first('div').hasClass('highlight')) {
+ removeCurrent();
+ current.prev('div').addClass('highlight').attr('aria-selected', true);
+ $input.attr("aria-activedescendant", current.prev('div').attr('id'));
+ }
+ else {
+ // Go to bottom of list
+ removeCurrent();
+ current = $results.children().last('div');
+ current.addClass('highlight').attr('aria-selected', true);
+ $input.attr("aria-activedescendant", current.attr('id'));
+ }
+ }
+
+ function moveDown(highlighted) {
+ $input.removeAttr("aria-activedescendant");
+
+ // if highlighted exists and if the highlighted item is not the last option
+ if (highlighted && !$results.children().last('div').hasClass('highlight')) {
+ removeCurrent();
+ current.next('div').addClass('highlight').attr('aria-selected', true);
+ $input.attr("aria-activedescendant", current.next('div').attr('id'));
+ }
+ else {
+ // Go to top of list
+ removeCurrent();
+ current = $results.children().first('div');
+ current.addClass('highlight').attr('aria-selected', true);
+ $input.attr("aria-activedescendant", current.attr('id'));
+ }
+ }
+
+ function removeCurrent() {
+ current = $results.find('.highlight');
+ current.attr('aria-selected', false);
+ current.removeClass('highlight');
+ }
+
+ function selectOption(highlighted, href) {
+ if (highlighted && href) { // @todo add logic for non-link suggestions
+ // Emit an event whose data can be used to write to analytics, etc.
+ $(document).trigger("SearchApiFederatedSolr::block::autocomplete::selection", [{
+ referrer: $(location).attr('href'),
+ target: href,
+ term: $input.val()
+ }]);
+ // Redirect to the selected link.
+ $(location).attr("href", href);
+ }
+ else {
+ return;
+ }
+ }
+ });
+ }
+ };
+
+ Drupal.SearchApiFederatedSolrAutocomplete = autocomplete;
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/search_api_federated_solr.install b/search_api_federated_solr.install
new file mode 100644
index 000000000..46d991ac0
--- /dev/null
+++ b/search_api_federated_solr.install
@@ -0,0 +1,34 @@
+getEditable('search_api_federated_solr.search_app.settings');
+
+ // Set the autocomplete config defaults.
+ $config->set('autocomplete.isEnabled', 0);
+ $config->set('autocomplete.url', '');
+ $config->set('autocomplete.appendWildcard', 0);
+ $config->set('autocomplete.suggestionRows', '');
+ $config->set('autocomplete.mode', 'result');
+ $config->set('autocomplete.result.titleText', '');
+ $config->set('autocomplete.result.hideDirectionsText', 0);
+
+ // Set the hidden facet/filter defaults.
+ $config->set('facet.site_name.is_hidden', false);
+ $config->set('facet.federated_terms.is_hidden', false);
+ $config->set('facet.federated_type.is_hidden', false);
+ $config->set('filter.federated_date.is_hidden', false);
+
+ // Set the index defaults for property flags.
+ $config->set('index.has_federated_date_property', false);
+ $config->set('index.has_federated_term_property', false);
+ $config->set('index.has_federated_type_property', false);
+
+ $config->save(TRUE);
+}
diff --git a/search_api_federated_solr.libraries.yml b/search_api_federated_solr.libraries.yml
index ee8157fbf..a3d5ec0a8 100644
--- a/search_api_federated_solr.libraries.yml
+++ b/search_api_federated_solr.libraries.yml
@@ -2,10 +2,23 @@ search:
version: 1.x
css:
theme:
- https://cdn.jsdelivr.net/gh/palantirnet/federated-search-react@v1.0.10/css/main.cf6a58ce.css:
+ https://cdn.jsdelivr.net/gh/palantirnet/federated-search-react@v2.0/css/main.ec684809.css:
type: external
minified: true
js:
- https://cdn.jsdelivr.net/gh/palantirnet/federated-search-react@v1.0.10/js/main.d41fc3fe.js:
+ https://cdn.jsdelivr.net/gh/palantirnet/federated-search-react@v2.0/js/main.6a547bbe.js:
preprocess: false
minified: true
+
+search_form_autocomplete:
+ css:
+ theme:
+ css/search_api_federated_solr_autocomplete.css: {}
+ js:
+ js/search_api_federated_solr_autocomplete.js: {}
+ dependencies:
+ - core/jquery
+ - core/drupal
+ - core/drupalSettings
+ - core/drupal.ajax
+ - core/drupal.announce
diff --git a/search_api_federated_solr.module b/search_api_federated_solr.module
index 6d56afdac..c71d26452 100644
--- a/search_api_federated_solr.module
+++ b/search_api_federated_solr.module
@@ -144,37 +144,3 @@ function search_api_federated_solr_form_federated_search_page_block_form_alter(&
$form['form_token']['#access'] = FALSE;
$form['form_id']['#access'] = FALSE;
}
-
-/**
- * Implements hook_form_FORM_ID_alter() for search_api_federated_solr_search_settings_form.
- *
- * Validates whether or not the search app's chosen index has a site_name
- * property and alters the search app settings form accordingly.
- *
- * @see \Drupal\search_api_federated_solr\Form\SearchApiFederatedSolrSearchAppSettingsForm
- */
-function search_api_federated_solr_form_search_api_federated_solr_search_app_settings_alter(&$form, FormStateInterface $form_state) {
- if ($search_index_id = $form['search_index']['#default_value']) {
- $config = \Drupal::configFactory()->getEditable('search_api_federated_solr.search_app.settings');
- $index_config = \Drupal::config('search_api.index.' . $search_index_id);
- // Determine if the index has a site name property, which could have been
- // added / removed since last form load.
- $site_name_property = $index_config->get('field_settings.site_name.configuration.site_name');
- $config->set('index.has_site_name_property', $site_name_property ? TRUE : FALSE);
-
- // If the index doesn't have a site name property.
- if (!$site_name_property) {
- // Reset the search app config options.
- $form['site_name_property']['#value'] = '';
- $form['set_search_site']['#default_value'] = 0;
- $config->set('facet.site_name.set_default', 0);
- }
- else {
- // Ensure the hidden form field reflects lack of site name property.
- $form['site_name_property']['#value'] = 'true';
- }
-
- $config->save();
- }
-
-}
diff --git a/src/Controller/SearchController.php b/src/Controller/SearchController.php
index ddced35a1..aa960d85e 100644
--- a/src/Controller/SearchController.php
+++ b/src/Controller/SearchController.php
@@ -34,7 +34,9 @@ public function searchPage() {
* The username and password will be
* combined and base64 encoded as per the application.
*/
- $federated_search_app_config['userpass'] = base64_encode($config->get('index.username') . ':' . $config->get('index.password'));
+ $username = $config->get('index.username');
+ $pass = $config->get('index.password');
+ $federated_search_app_config['userpass'] = $username && $pass ? base64_encode($config->get('index.username') . ':' . $config->get('index.password')) : '';
// Validate that there is still a site name property set for this index.
$site_name_property = $index_config->get('field_settings.site_name.configuration.site_name');
@@ -52,6 +54,36 @@ public function searchPage() {
$config->set('facet.site_name.set_default', 0);
}
+ // Create an index property field map array to determine which fields
+ // exist on the index and should be hidden in the app UI.
+ $search_fields = [
+ "sm_site_name" => [
+ "property" => $site_name_property,
+ "is_hidden" => $config->get('facet.site_name.is_hidden'),
+ ],
+ "ss_federated_type" => [
+ "property" => $config->get('index.has_federated_type_property'),
+ "is_hidden" => $config->get('facet.federated_type.is_hidden'),
+ ],
+ "ds_federated_date" => [
+ "property" => $config->get('index.has_federated_date_property'),
+ "is_hidden" => $config->get('filter.federated_date.is_hidden'),
+ ],
+ "sm_federated_terms" => [
+ "property" => $config->get('index.has_federated_terms_property'),
+ "is_hidden" => $config->get('facet.federated_terms.is_hidden'),
+ ],
+ ];
+
+ // Set hiddenSearchFields to an array of keys of those $search_fields items
+ // which both exist as an index property and are set to be hidden.
+
+ // OPTIONAL: Machine name of those search fields whose facets/filter and
+ // current values should be hidden in UI.
+ $federated_search_app_config['hiddenSearchFields'] = array_keys(array_filter($search_fields, function ($value) {
+ return $value['property'] && $value['is_hidden'];
+ }));
+
// OPTIONAL: The text to display when the app loads with no search term.
if ($search_prompt = $config->get('content.search_prompt')) {
$federated_search_app_config['searchPrompt'] = $search_prompt;
@@ -82,6 +114,38 @@ public function searchPage() {
$federated_search_app_config['pageTitle'] = $page_title;
}
+ $federated_search_app_config['autocomplete'] = FALSE;
+ if ($autocomplete_is_enabled = $config->get('autocomplete.isEnabled')) {
+ // REQUIRED: Autocomplete endpoint, defaults to main search url
+ if ($autocomplete_url = $config->get('autocomplete.url')) {
+ $federated_search_app_config['autocomplete']['url'] = $autocomplete_url;
+ }
+ // OPTIONAL: defaults to false, whether or not to append wildcard to query term
+ if ($autocomplete_append_wildcard = $config->get('autocomplete.appendWildcard')) {
+ $federated_search_app_config['autocomplete']['appendWildcard'] = $autocomplete_append_wildcard;
+ }
+ // OPTIONAL: defaults to 5, max number of autocomplete results to return
+ if ($autocomplete_suggestion_rows = $config->get('autocomplete.suggestionRows')) {
+ $federated_search_app_config['autocomplete']['suggestionRows'] = $autocomplete_suggestion_rows;
+ }
+ // OPTIONAL: defaults to 2, number of characters *after* which autocomplete results should appear
+ if ($autocomplete_num_chars = $config->get('autocomplete.numChars')) {
+ $federated_search_app_config['autocomplete']['numChars'] = $autocomplete_num_chars;
+ }
+ // REQUIRED: show search-as-you-type results ('result', default) or search term ('term') suggestions
+ if ($autocomplete_mode = $config->get('autocomplete.mode')) {
+ $federated_search_app_config['autocomplete']['mode'] = $autocomplete_mode;
+ // OPTIONAL: default set, title to render above autocomplete results
+ if ($autocomplete_mode_title_text = $config->get('autocomplete.' . $autocomplete_mode . '.titleText')) {
+ $federated_search_app_config['autocomplete'][$autocomplete_mode]['titleText'] = $autocomplete_mode_title_text;
+ }
+ // OPTIONAL: defaults to false, whether or not to hide the keyboard usage directions text
+ if ($autocomplete_mode_hide_directions = $config->get('autocomplete.' . $autocomplete_mode . '.hideDirectionsText')) {
+ $federated_search_app_config['autocomplete'][$autocomplete_mode]['showDirectionsText'] = FALSE;
+ }
+ }
+ }
+
$element = [
'#theme' => 'search_app',
'#federated_search_app_config' => $federated_search_app_config,
diff --git a/src/Form/FederatedSearchPageBlockForm.php b/src/Form/FederatedSearchPageBlockForm.php
index c687a006f..4bd25617a 100644
--- a/src/Form/FederatedSearchPageBlockForm.php
+++ b/src/Form/FederatedSearchPageBlockForm.php
@@ -42,6 +42,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
'#attributes' => [
'title' => $this->t('Enter the terms you wish to search for.'),
'placeholder' => 'Search',
+ 'autocomplete' => "off", // refers to html attribute, not our custom autocomplete.
],
'#provider' => 'search_api_federated_solr',
];
diff --git a/src/Form/SearchApiFederatedSolrSearchAppSettingsForm.php b/src/Form/SearchApiFederatedSolrSearchAppSettingsForm.php
index e99a83fb1..e531804cf 100644
--- a/src/Form/SearchApiFederatedSolrSearchAppSettingsForm.php
+++ b/src/Form/SearchApiFederatedSolrSearchAppSettingsForm.php
@@ -48,15 +48,71 @@ public function buildForm(array $form, FormStateInterface $form_state) {
$index_options[$search_api_index->id()] = $search_api_index->label();
}
- $form['path'] = [
+ /**
+ * Set index config values to indicate which properties
+ */
+ $site_name_property_value = '';
+ $site_name_property_default_value = '';
+ // Validates whether or not the search app's chosen index has a site_name,
+ // federated_date, federated_type, and federated_terms properties
+ // and alters the search app settings form accordingly.
+ if ($search_index_id = $config->get('index.id')) {
+ $index_config = \Drupal::config('search_api.index.' . $search_index_id);
+ // Determine if the index has a site name property, which could have been
+ // added / removed since last form load.
+ $site_name_property = $index_config->get('field_settings.site_name.configuration.site_name');
+ $config->set('index.has_site_name_property', $site_name_property ? TRUE : FALSE);
+
+ // If the index does have a site name property, ensure the hidden form field reflects that.
+ if ($site_name_property) {
+ $site_name_property_value = 'true';
+ $site_name_property_default_value = 'true';
+ }
+ else {
+ // Assume properties are not present, set defaults.
+ $site_name_property_value = '';
+ $site_name_property_default_value = FALSE;
+ $config->set('facet.site_name.set_default', FALSE);
+ }
+
+ // Save config indicating which index field properties that
+ // correspond to facets and filters are present on the index.
+ $type_property = $index_config->get('field_settings.federated_type');
+ $config->set('index.has_federated_type_property', $type_property ? TRUE : FALSE);
+
+ $date_property = $index_config->get('field_settings.federated_date');
+ $config->set('index.has_federated_date_property', $date_property ? TRUE : FALSE);
+
+ $terms_property = $index_config->get('field_settings.federated_terms');
+ $config->set('index.has_federated_terms_property', $terms_property ? TRUE : FALSE);
+
+ $config->save();
+ }
+
+ /**
+ * Basic set up:
+ * - search results page path
+ * - search results page title
+ * - autocomplete enable triggers display of autocopmlete config fieldset
+ * - serach index to use as datasource,
+ * - basic auth credentials for index
+ */
+
+ $form['setup'] = [
+ '#type' => 'details',
+ '#title' => 'Search Results Page > Set Up',
+ '#open' => TRUE,
+ ];
+
+ $form['setup']['path'] = [
'#type' => 'textfield',
- '#title' => $this->t('Search app path'),
+ '#title' => $this->t('Search results page path'),
'#default_value' => $config->get('path'),
'#description' => $this
->t('The path for the search app (Default: "/search-app").'),
];
- $form['page_title'] = [
+ $form['setup']['page_title'] = [
'#type' => 'textfield',
'#title' => $this->t('Search results page title'),
'#default_value' => $config->get('page_title'),
@@ -64,10 +120,10 @@ public function buildForm(array $form, FormStateInterface $form_state) {
->t('The title that will live in the header tag of the search results page (leave empty to hide completely).'),
];
- $form['search_index'] = [
+ $form['setup']['search_index'] = [
'#type' => 'select',
'#title' => $this->t('Search API index'),
- '#description' => $this->t('Defines which search_api index and server the search app should use.'),
+ '#description' => $this->t('Defines which search_api index and server the search app should use as a datasource.'),
'#options' => $index_options,
'#default_value' => $config->get('index.id'),
'#required' => TRUE,
@@ -78,56 +134,49 @@ public function buildForm(array $form, FormStateInterface $form_state) {
],
];
- $form['site_name_property'] = [
- '#type' => 'hidden',
- '#attributes' => [
- 'id' => ['site-name-property'],
- ],
- '#value' => $config->get('index.has_site_name_property') ? 'true' : '',
- ];
-
- $form['search_index_basic_auth'] = [
+ $form['setup']['search_index_basic_auth'] = [
'#type' => 'fieldset',
'#title' => $this->t('Search Index Basic Authentication'),
'#description' => $this->t('If your Solr server is protected by basic HTTP authentication, enter the login data here. This will be accessible to the client in an obscured, but non-secure method. It should, therefore, only provide read access to the index AND be different from that provided when configuring the server in Search API. The Password field is intentionally not obscured to emphasize this distinction.'),
];
- $form['search_index_basic_auth']['username'] = [
+ $form['setup']['search_index_basic_auth']['username'] = [
'#type' => 'textfield',
'#title' => $this->t('Username'),
'#default_value' => $config->get('index.username'),
];
- $form['search_index_basic_auth']['password'] = [
+ $form['setup']['search_index_basic_auth']['password'] = [
'#type' => 'textfield',
'#title' => $this->t('Password'),
'#default_value' => $config->get('index.password'),
];
- $form['set_search_site'] = [
- '#type' => 'checkbox',
- '#title' => $this->t('Set the "Site name" facet to this site'),
- '#default_value' => $config->get('facet.site_name.set_default'),
- '#description' => $this
- ->t('When checked, only search results from this site will be shown, by default, until this site\'s checkbox is unchecked in the search app\'s "Site name" facet.'),
- '#states' => [
- 'visible' => [
- ':input[name="site_name_property"]' => [
- 'value' => "true",
- ],
- ],
- ],
+ /**
+ * Search results page options:
+ * - show empty search results (i.e. filterable listing page),
+ * - customize "no results" text
+ * - custom search prompt
+ * - renders in result area when show empty results no enabled and no query value
+ * - max number of search results per page
+ * - max number of "numbered" pagination buttons to show
+ */
+
+ $form['search_page_options'] = [
+ '#type' => 'details',
+ '#title' => 'Search Results Page > Options',
+ '#open' => FALSE,
];
- $form['show_empty_search_results'] = [
+ $form['search_page_options']['show_empty_search_results'] = [
'#type' => 'checkbox',
- '#title' => $this->t('Show results for empty search'),
+ '#title' => '' . $this->t('Show results for empty search') . '',
'#default_value' => $config->get('content.show_empty_search_results'),
'#description' => $this
->t(' When checked, this option allows users to see all results when no search term is entered. By default, empty searches are disabled and yield no results.'),
];
- $form['no_results_text'] = [
+ $form['search_page_options']['no_results_text'] = [
'#type' => 'textfield',
'#title' => $this->t('No results text'),
'#default_value' => $config->get('content.no_results'),
@@ -135,7 +184,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
->t('This text is shown when a query returns no results. (Default: "Your search yielded no results.")'),
];
- $form['search_prompt_text'] = [
+ $form['search_page_options']['search_prompt_text'] = [
'#type' => 'textfield',
'#title' => $this->t('Search prompt text'),
'#default_value' => $config->get('content.search_prompt'),
@@ -143,7 +192,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
->t('This text is shown when no query term has been entered. (Default: "Please enter a search term.")'),
];
- $form['rows'] = [
+ $form['search_page_options']['rows'] = [
'#type' => 'number',
'#title' => $this->t('Number of search results per page'),
'#default_value' => $config->get('results.rows'),
@@ -151,7 +200,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
->t('The max number of results to render per search results page. (Default: 20)'),
];
- $form['page_buttons'] = [
+ $form['search_page_options']['page_buttons'] = [
'#type' => 'number',
'#title' => $this->t('Number of pagination buttons'),
'#default_value' => $config->get('pagination.buttons'),
@@ -159,6 +208,297 @@ public function buildForm(array $form, FormStateInterface $form_state) {
->t('The max number of numbered pagination buttons to show at a given time. (Default: 5)'),
];
+ /**
+ * Settings and values for search facets and filters:
+ * - set the site name facet to the current site name property
+ */
+
+ $form['search_form_values'] = [
+ '#type' => 'details',
+ '#title' => 'Search Results Page > Facets & Filters',
+ '#open' => FALSE,
+ ];
+
+ /**
+ * Set hidden form element value based on presence of field properties on
+ * the selected index. This value will determine which inputs are
+ * visible for setting default facet/filter values and hiding in the UI.
+ */
+
+ $form['search_form_values']['site_name_property'] = [
+ '#type' => 'hidden',
+ '#attributes' => [
+ 'id' => ['site-name-property'],
+ ],
+ '#value' => $site_name_property_value,
+ '#default_value' => $site_name_property_default_value,
+ ];
+
+ $form['search_form_values']['date_property'] = [
+ '#type' => 'hidden',
+ '#attributes' => [
+ 'id' => ['date-property'],
+ ],
+ '#value' => $config->get('index.has_federated_date_property') ? 'true' : '',
+ ];
+
+ $form['search_form_values']['type_property'] = [
+ '#type' => 'hidden',
+ '#attributes' => [
+ 'id' => ['type-property'],
+ ],
+ '#value' => $config->get('index.has_federated_type_property') ? 'true' : '',
+ ];
+
+ $form['search_form_values']['terms_property'] = [
+ '#type' => 'hidden',
+ '#attributes' => [
+ 'id' => ['terms-property'],
+ ],
+ '#value' => $config->get('index.has_federated_terms_property') ? 'true' : '',
+ ];
+
+ /**
+ * Enable setting of default values for available facets / filter.
+ * As of now, this includes Site Name only.
+ */
+
+ $form['search_form_values']['defaults'] = [
+ '#type' => 'fieldset',
+ '#title' => 'Set facet / filter default values'
+ ];
+
+ $form['search_form_values']['defaults']['set_search_site'] = [
+ '#type' => 'checkbox',
+ '#title' => $this->t('Set the "Site name" facet to this site'),
+ '#default_value' => $config->get('facet.site_name.set_default'),
+ '#description' => $this
+ ->t('When checked, only search results from this site will be shown, by default, until this site\'s checkbox is unchecked in the search app\'s "Site name" facet.'),
+ '#states' => [
+ 'visible' => [
+ ':input[name="site_name_property"]' => [
+ 'value' => "true",
+ ],
+ ],
+ ],
+ ];
+
+ /**
+ * Enable hiding available facets / filters.
+ * These form elements will only be visible if their corresopnding
+ * property exists on the index.
+ */
+ $form['search_form_values']['hidden'] = [
+ '#type' => 'fieldset',
+ '#title' => $this->t('Hide facets / filters from sidebar'),
+ '#description' => $this->t('The checked facets / filters will be hidden from the search app.'),
+ ];
+
+ $form['search_form_values']['hidden']['hide_site_name'] = [
+ '#type' => 'checkbox',
+ '#title' => $this->t('Site name facet'),
+ '#default_value' => $config->get('facet.site_name.is_hidden'),
+ '#description' => $this
+ ->t('When checked, the ability to which sites should be included in the results will be hidden.'),
+ '#states' => [
+ 'visible' => [
+ ':input[name="site_name_property"]' => [
+ 'value' => "true",
+ ],
+ ],
+ ],
+ ];
+
+ $form['search_form_values']['hidden']['hide_type'] = [
+ '#type' => 'checkbox',
+ '#title' => $this->t('Type facet'),
+ '#default_value' => $config->get('facet.federated_type.is_hidden'),
+ '#description' => $this
+ ->t('When checked, the ability to select those types (i.e. bundles) which should have results returned will be hidden.'),
+ '#states' => [
+ 'visible' => [
+ ':input[name="type_property"]' => [
+ 'value' => "true",
+ ],
+ ],
+ ],
+ ];
+
+ $form['search_form_values']['hidden']['hide_date'] = [
+ '#type' => 'checkbox',
+ '#title' => $this->t('Date filter'),
+ '#default_value' => $config->get('filter.federated_date.is_hidden'),
+ '#description' => $this
+ ->t('When checked, the ability to filter results by date will be hidden.'),
+ '#states' => [
+ 'visible' => [
+ ':input[name="date_property"]' => [
+ 'value' => "true",
+ ],
+ ],
+ ],
+ ];
+
+ $form['search_form_values']['hidden']['hide_terms'] = [
+ '#type' => 'checkbox',
+ '#title' => $this->t('Terms facet'),
+ '#default_value' => $config->get('facet.federated_terms.is_hidden'),
+ '#description' => $this
+ ->t('When checked, the ability to select those terms which should have results returned will be hidden.'),
+ '#states' => [
+ 'visible' => [
+ ':input[name="terms_property"]' => [
+ 'value' => "true",
+ ],
+ ],
+ ],
+ ];
+
+ /**
+ * Autocomplete settings:
+ * - endpoint URL
+ * - use wildcard to support partial terms
+ * - customize number of autocomplete results
+ * - number of characters after which autocomplete query should be executed
+ * - autocomplete results mode (search results, terms)
+ * - title for autocomplete results
+ * - show/hide autocomplete keyboard directions
+ */
+
+ $form['autocomplete'] = [
+ '#type' => 'details',
+ '#title' => $this->t('Search Results Page > Search Form > Autocomplete'),
+ '#description' => $this->t('These options apply to the autocomplete functionality on the search for which appears above the search results on the search results page. Configure your placed Federated Search Page Form block to add autocomplete to that form.'),
+ '#open' => $config->get('autocomplete.isEnabled'),
+ ];
+
+ $form['autocomplete']['autocomplete_is_enabled'] = [
+ '#type' => 'checkbox',
+ '#title' => '' . $this->t('Enable autocomplete for the search results page search form') . '',
+ '#default_value' => $config->get('autocomplete.isEnabled'),
+ '#description' => $this
+ ->t('Check this box to enable autocomplete on the search results page search form and to expose more configuration options below.'),
+ '#attributes' => [
+ 'data-autocomplete-enabler' => TRUE,
+ ],
+ ];
+
+ $form['autocomplete']['autocomplete_is_append_wildcard'] = [
+ '#type' => 'checkbox',
+ '#title' => '' . $this->t('Append a wildcard \'*\' to support partial text search') . '',
+ '#default_value' => $config->get('autocomplete.appendWildcard'),
+ '#description' => $this
+ ->t('Check this box to append a wildcard * to the end of the autocomplete query term (i.e. "car" becomes "car+car*"). This option is recommended if your solr config does not add a field(s) with NGram Tokenizers to your index or if your autocomplete Request Handler is not configured to search those fields.'),
+ '#states' => [
+ 'visible' => [
+ ':input[data-autocomplete-enabler]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
+ $form['autocomplete']['autocomplete_url'] = [
+ '#type' => 'url',
+ '#title' => $this->t('Solr Endpoint URL'),
+ '#default_value' => $config->get('autocomplete.url'),
+ '#maxlength' => 2048,
+ '#size' => 50,
+ '#description' => $this
+ ->t('The URL where requests for autocomplete queries should be made. (Default: the url of the selectRequest Handler on the server of the selected Search API index.)
Supports an absolute url pattern to any other Request Handler for an index on your solr server
The value of the main search field will be appended to the url as the main query param (i.e. ?q=[value of the search field, wildcard appended if enabled])
Any facet/filter default values set for the search app will automatically be appended (i.e. &sm_site_name=[value of the site name for the index])
The format param &wt=json will automatically be appended
Include any other necessary url params corresponding to query parameters.
'),
+ '#states' => [
+ 'visible' => [
+ ':input[data-autocomplete-enabler]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
+ $form['autocomplete']['autocomplete_suggestion_rows'] = [
+ '#type' => 'number',
+ '#title' => $this->t('Number of results'),
+ '#default_value' => $config->get('autocomplete.suggestionRows'),
+ '#description' => $this
+ ->t('The max number of results to render in the autocomplete results dropdown. (Default: 5)'),
+ '#states' => [
+ 'visible' => [
+ ':input[data-autocomplete-enabler]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
+ $form['autocomplete']['autocomplete_num_chars'] = [
+ '#type' => 'number',
+ '#title' => $this->t('Number of characters after which autocomplete query should execute'),
+ '#default_value' => $config->get('autocomplete.numChars'),
+ '#description' => $this
+ ->t('Autocomplete query will be executed after a user types this many characters in the search query field. (Default: 2)'),
+ '#states' => [
+ 'visible' => [
+ ':input[data-autocomplete-enabler]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
+ $autocomplete_mode = $config->get('autocomplete.mode');
+ $title_text_config_key = 'autocomplete.' . $autocomplete_mode . '.titleText';
+ $hide_directions_text_config_key = 'autocomplete.' . $autocomplete_mode . '.hideDirectionsText';
+
+ $form['autocomplete']['autocomplete_mode'] = [
+ '#type' => 'select',
+ '#title' => $this->t('Autocomplete mode'),
+ '#description' => $this->t('Type of results the autocomplete response returns: search results (default) or search terms.'),
+ '#options' => [
+ 'result' => $this
+ ->t('Search results (i.e. search as you type functionality)'),
+ 'Search terms (coming soon)' => [],
+ ],
+ '#default_value' => $autocomplete_mode || 'result',
+ '#states' => [
+ 'visible' => [
+ ':input[data-autocomplete-enabler]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
+ $form['autocomplete']['autocomplete_mode_title_text'] = [
+ '#type' => 'textfield',
+ '#title' => $this->t('Results title text'),
+ '#size' => 50,
+ '#default_value' => $autocomplete_mode ? $config->get($title_text_config_key) : '',
+ '#description' => $this
+ ->t('The title text is shown above the results in the autocomplete drop down. (Default: "What are you interested in?" for Search Results mode and "What would you like to search for?" for Search Term mode.)'),
+ '#states' => [
+ 'visible' => [
+ ':input[data-autocomplete-enabler]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
+ $form['autocomplete']['autocomplete_mode_hide_directions'] = [
+ '#type' => 'checkbox',
+ '#title' => '' . $this->t('Hide keyboard directions') . '',
+ '#default_value' => $autocomplete_mode ? $config->get($hide_directions_text_config_key) : 0,
+ '#description' => $this
+ ->t('Check this box to make hide the autocomplete keyboard usage directions in the results dropdown. For sites that want to maximize their accessibility UX for sighted keyboard users, we recommend leaving this unchecked. (Default: directions are visible)'),
+ '#states' => [
+ 'visible' => [
+ ':input[data-autocomplete-enabler]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
$form['#cache'] = ['max-age' => 0];
return parent::buildForm($form, $form_state);
@@ -188,6 +528,19 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
$set_search_site = $form_state->getValue('set_search_site');
$config->set('facet.site_name.set_default', $set_search_site);
+ // Set the search app config settings for hidden filter/facets.
+ $hide_search_site = $form_state->getValue('hide_site_name');
+ $config->set('facet.site_name.is_hidden', $hide_search_site);
+
+ $hide_type = $form_state->getValue('hide_type');
+ $config->set('facet.federated_type.is_hidden', $hide_type);
+
+ $hide_terms = $form_state->getValue('hide_terms');
+ $config->set('facet.federated_terms.is_hidden', $hide_terms);
+
+ $hide_date = $form_state->getValue('hide_date');
+ $config->set('filter.federated_date.is_hidden', $hide_date);
+
// Set the search app configuration setting for the default search site flag.
$show_empty_search_results = $form_state->getValue('show_empty_search_results');
$config->set('content.show_empty_search_results', $show_empty_search_results);
@@ -197,12 +550,8 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
// Save the selected index option in search app config (for form state).
$config->set('index.id', $search_index);
- // Get the index configuration object.
- $index_config = \Drupal::config('search_api.index.' . $search_index);
- $site_name_property = $index_config->get('field_settings.site_name.configuration.site_name');
- $config->set('index.has_site_name_property', $site_name_property ? TRUE : FALSE);
-
// Get the id of the chosen index's server.
+ $index_config = \Drupal::config('search_api.index.' . $search_index);
$index_server = $index_config->get('server');
// Get the server url.
@@ -235,6 +584,32 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
// Set the number of pagination buttons.
$config->set('pagination.buttons', $form_state->getValue('page_buttons'));
+ // Set autocomplete options.
+ $autocomplete_is_enabled = $form_state->getValue('autocomplete_is_enabled');
+ $config->set('autocomplete.isEnabled', $form_state->getValue('autocomplete_is_enabled'));
+
+ // If enabled, set the autocomplete options.
+ if ($autocomplete_is_enabled) {
+ // Cache form values that we'll use more than once.
+ $autocomplete_url_value = $form_state->getValue('autocomplete_url');
+ $autocomplete_mode = $form_state->getValue('autocomplete_mode');
+
+ // Set the default autocomplete endpoint url to the default search url if none was passed in.
+ $autocomplete_url = $autocomplete_url_value ? $autocomplete_url_value : $server_url;
+
+ // Set the actual autocomplete config options.
+ $config->set('autocomplete.url', $autocomplete_url);
+ $config->set('autocomplete.appendWildcard', $form_state->getValue('autocomplete_is_append_wildcard'));
+ $config->set('autocomplete.suggestionRows', $form_state->getValue('autocomplete_suggestion_rows'));
+ $config->set('autocomplete.numChars', $form_state->getValue('autocomplete_num_chars'));
+ if ($autocomplete_mode) {
+ $config->set('autocomplete.mode', $autocomplete_mode);
+ $title_text_config_key = 'autocomplete.' . $autocomplete_mode . '.titleText';
+ $config->set($title_text_config_key, $form_state->getvalue('autocomplete_mode_title_text'));
+ $hide_directions_config_key = 'autocomplete.' . $autocomplete_mode . '.hideDirectionsText';
+ $config->set($hide_directions_config_key, $form_state->getValue('autocomplete_mode_hide_directions'));
+ }
+ }
$config->save();
if ($rebuild_routes) {
diff --git a/src/Plugin/Block/FederatedSearchPageFormBlock.php b/src/Plugin/Block/FederatedSearchPageFormBlock.php
index 53aef6e63..67cc81aac 100644
--- a/src/Plugin/Block/FederatedSearchPageFormBlock.php
+++ b/src/Plugin/Block/FederatedSearchPageFormBlock.php
@@ -4,6 +4,7 @@
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Block\BlockPluginInterface;
+use Drupal\Core\Form\FormStateInterface;
/**
* Provides a "Federated Search Page Form" block.
@@ -20,12 +21,329 @@ class FederatedSearchPageFormBlock extends BlockBase implements BlockPluginInter
* {@inheritdoc}
*/
public function build() {
+ $config = $this->getConfiguration();
+
$build = [
'#theme' => 'search_api_federated_solr_block',
'#search_form' => \Drupal::formBuilder()->getForm('Drupal\search_api_federated_solr\Form\FederatedSearchPageBlockForm'),
];
+
+ // If autocomplete is enabled for this block, attach the js library.
+ if (array_key_exists('autocomplete',$config)
+ && array_key_exists('isEnabled', $config['autocomplete'])
+ && $config['autocomplete']['isEnabled'] === 1) {
+ // Attach autocomplete JS library.
+ $build['#attached']['library'][] = 'search_api_federated_solr/search_form_autocomplete';
+ // Write the block config to Drupal settings.
+ $build['#attached']['drupalSettings']['searchApiFederatedSolr'] = [
+ 'block' => [
+ 'autocomplete' => $config['autocomplete'],
+ ],
+ ];
+ // Add the js trigger class to the block.
+ $build['#attributes']['class'][] = 'js-search-api-federated-solr-block-form-autocomplete';
+ }
+
return $build;
}
+ /**
+ * {@inheritdoc}
+ */
+ public function blockForm($form, FormStateInterface $form_state) {
+ $form = parent::blockForm($form, $form_state);
+
+ $config = $this->getConfiguration();
+
+ $index_options = [];
+ $search_api_indexes = \Drupal::entityTypeManager()->getStorage('search_api_index')->loadMultiple();
+ /* @var $search_api_index \Drupal\search_api\IndexInterface */
+ foreach ($search_api_indexes as $search_api_index) {
+ $index_options[$search_api_index->id()] = $search_api_index->label();
+ }
+
+ /**
+ * Autocomplete settings:
+ * - endpoint URL
+ * - use wildcard to support partial terms
+ * - customize number of autocomplete results
+ * - number of characters after which autocomplete query should be executed
+ * - autocomplete results mode (search results, terms)
+ * - title for autocomplete results
+ * - show/hide autocomplete keyboard directions
+ */
+
+ $form['autocomplete'] = [
+ '#type' => 'details',
+ '#title' => $this->t('Federated Search Page Form Block > Search Form > Autocomplete'),
+ '#description' => $this->t('These options apply to the autocomplete functionality on the search form which appears on pages that render the Federated Search Page Form Block. Configure the search results page search form functionality on the Federated Search App settings page.'),
+ '#open' => TRUE,
+ ];
+
+ $form['autocomplete']['autocomplete_is_enabled'] = [
+ '#type' => 'checkbox',
+ '#title' => '' . $this->t('Enable autocomplete for the search results page search form') . '',
+ '#default_value' => isset($config['autocomplete']['isEnabled']) ? $config['autocomplete']['isEnabled'] : 0,
+ '#description' => $this
+ ->t('Check this box to enable autocomplete on the federated search block search form and to expose more configuration options below.'),
+ '#attributes' => [
+ 'data-autocomplete-enable' => TRUE,
+ ],
+ ];
+
+ $form['autocomplete']['autocomplete_is_append_wildcard'] = [
+ '#type' => 'checkbox',
+ '#title' => '' . $this->t('Append a wildcard \'*\' to support partial text search') . '',
+ '#default_value' => isset($config['autocomplete']['appendWildcard']) ? $config['autocomplete']['appendWildcard'] : 0,
+ '#description' => $this
+ ->t('Check this box to append a wildcard * to the end of the autocomplete query term (i.e. "car" becomes "car+car*"). This option is recommended if your solr config does not add a field(s) with NGram Tokenizers to your index or if your autocomplete Request Handler is not configured to search those fields.'),
+ '#states' => [
+ 'visible' => [
+ ':input[data-autocomplete-enable]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
+ $form['autocomplete']['autocomplete_url'] = [
+ '#type' => 'url',
+ '#title' => $this->t('Endpoint URL'),
+ '#default_value' => isset($config['autocomplete']['url']) ? $config['autocomplete']['url'] : '',
+ '#maxlength' => 2048,
+ '#size' => 50,
+ '#description' => $this
+ ->t('The URL where requests for autocomplete queries should be made. (Default: the url of the selectRequest Handler on the server of the selected Search API index.)
Supports absolute url pattern to any endpoint which returns the expected response structure:
+
{
+ response: {
+ docs: [
+ {
+ ss_federated_title: [result title to be used as link text],
+ ss_url: [result url to be used as link href],
+ }
+ ]
+ }
+}
Include [val] in the URL to indicate where you would like the form value to be inserted: http://d8.fs-demo.local/search-api-federated-solr-block-form-autocomplete/search-view?title=[val]&_format=json
Any facet/filter default values set for the search app will automatically be appended (i.e. &sm_site_name=[value of the site name for the index])
Include any other necessary url params (like &_format=json if you are using a Views Rest Export or &wt=json if you are using a different Request Handler on your Solr index.
'),
+ '#states' => [
+ 'visible' => [
+ ':input[data-autocomplete-enable]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
+ $form['autocomplete']['basic_auth'] = [
+ '#type' => 'fieldset',
+ '#title' => $this->t('Basic Authentication'),
+ '#description' => $this->t('If your endpoint is protected by basic HTTP authentication, enter the login data here. This will be accessible to the client in an obscured, but non-secure method. It should, therefore, only provide read access to the index AND be different from that provided when configuring the server in Search API. The Password field is intentionally not obscured to emphasize this distinction.'),
+ '#states' => [
+ 'visible' => [
+ ':input[data-autocomplete-enable]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
+ $form['autocomplete']['basic_auth']['use_search_app_creds'] = [
+ '#type' => 'checkbox',
+ '#title' => $this->t('Use credentials provided to search app'),
+ '#default_value' => isset($config['autocomplete']['use_search_app_creds']) ? $config['autocomplete']['use_search_app_creds'] : 0,
+ '#attributes' => [
+ 'data-autocomplete-use-search-app-creds' => TRUE,
+ ],
+ ];
+
+ $form['autocomplete']['basic_auth']['username'] = [
+ '#type' => 'textfield',
+ '#title' => $this->t('Username'),
+ '#default_value' => isset($config['autocomplete']['username']) ? $config['autocomplete']['username'] : '',
+ '#states' => [
+ 'visible' => [
+ ':input[data-autocomplete-use-search-app-creds]' => [
+ 'checked' => FALSE,
+ ],
+ ],
+ ],
+ ];
+
+ $form['autocomplete']['basic_auth']['password'] = [
+ '#type' => 'textfield',
+ '#title' => $this->t('Password'),
+ '#default_value' => isset($config['autocomplete']['password']) ? $config['autocomplete']['password'] : '',
+ '#states' => [
+ 'visible' => [
+ ':input[data-autocomplete-use-search-app-creds]' => [
+ 'checked' => FALSE,
+ ],
+ ],
+ ],
+ ];
+
+ $form['autocomplete']['autocomplete_suggestion_rows'] = [
+ '#type' => 'number',
+ '#title' => $this->t('Number of results'),
+ '#default_value' => isset($config['autocomplete']['suggestionRows']) ? $config['autocomplete']['suggestionRows'] : '',
+ '#description' => $this
+ ->t('The max number of results to render in the autocomplete results dropdown. (Default: 5)'),
+ '#states' => [
+ 'visible' => [
+ ':input[data-autocomplete-enable]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
+ $form['autocomplete']['autocomplete_num_chars'] = [
+ '#type' => 'number',
+ '#title' => $this->t('Number of characters after which autocomplete query should execute'),
+ '#default_value' => isset($config['autocomplete']['numChars']) ? $config['autocomplete']['numChars'] : '',
+ '#description' => $this
+ ->t('Autocomplete query will be executed after a user types this many characters in the search query field. (Default: 2)'),
+ '#states' => [
+ 'visible' => [
+ ':input[data-autocomplete-enable]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
+ $autocomplete_mode = isset($config['autocomplete']['mode']) ? $config['autocomplete']['mode'] : '';
+
+ $form['autocomplete']['autocomplete_mode'] = [
+ '#type' => 'select',
+ '#title' => $this->t('Autocomplete mode'),
+ '#description' => $this->t('Type of results the autocomplete response returns: search results (default) or search terms.'),
+ '#options' => [
+ 'result' => $this
+ ->t('Search results (i.e. search as you type functionality)'),
+ 'Search terms (coming soon)' => [],
+ ],
+ '#default_value' => isset($config['autocomplete']['mode']) ? $config['autocomplete']['mode'] : 'result',
+ '#states' => [
+ 'visible' => [
+ ':input[data-autocomplete-enable]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
+ $form['autocomplete']['autocomplete_mode_title_text'] = [
+ '#type' => 'textfield',
+ '#title' => $this->t('Results title text'),
+ '#size' => 50,
+ '#default_value' => $autocomplete_mode && isset($config['autocomplete'][$autocomplete_mode]['titleText']) ? $config['autocomplete'][$autocomplete_mode]['titleText'] : '',
+ '#description' => $this
+ ->t('The title text is shown above the results in the autocomplete drop down. (Default: "What are you interested in?" for Search Results mode and "What would you like to search for?" for Search Term mode.)'),
+ '#states' => [
+ 'visible' => [
+ ':input[data-autocomplete-enable]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
+ $form['autocomplete']['autocomplete_mode_hide_directions'] = [
+ '#type' => 'checkbox',
+ '#title' => '' . $this->t('Hide keyboard directions') . '',
+ '#default_value' => $autocomplete_mode && isset($config['autocomplete'][$autocomplete_mode]['hideDirectionsText']) ? $config['autocomplete'][$autocomplete_mode]['hideDirectionsText'] : 0,
+ '#description' => $this
+ ->t('Check this box to make hide the autocomplete keyboard usage directions in the results dropdown. For sites that want to maximize their accessibility UX for sighted keyboard users, we recommend leaving this unchecked. (Default: directions are visible)'),
+ '#states' => [
+ 'visible' => [
+ ':input[data-autocomplete-enable]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function blockSubmit($form, FormStateInterface $form_state) {
+ parent::blockSubmit($form, $form_state);
+ $values = $form_state->getValues();
+ // Set autocomplete options.
+ $autocomplete_is_enabled = $values['autocomplete']['autocomplete_is_enabled'];
+ $autocomplete['isEnabled'] = $autocomplete_is_enabled;
+
+ // If enabled, set the autocomplete options.
+ if ($autocomplete_is_enabled) {
+ // Cache form values that we'll use more than once.
+ $autocomplete_url_value = $values['autocomplete']['autocomplete_url'];
+ $autocomplete_mode = $values['autocomplete']['autocomplete_mode'];
+
+ // Set the default autocomplete endpoint url to the default search url if none was passed in.
+ // Get the id of the chosen index's server.
+ $app_config = \Drupal::config('search_api_federated_solr.search_app.settings');
+ $search_index = $app_config->get('index.id');
+ $index_config = \Drupal::config('search_api.index.' . $search_index);
+ $index_server = $index_config->get('server');
+
+ // Get the server url.
+ $server_config = \Drupal::config('search_api.server.' . $index_server);
+ $server = $server_config->get('backend_config.connector_config');
+ // Get the required server config field data.
+ $server_url = $server['scheme'] . '://' . $server['host'] . ':' . $server['port'];
+ // Check for the non-required server config field data before appending.
+ $server_url .= $server['path'] ?: '';
+ $server_url .= $server['core'] ? '/' . $server['core'] : '';
+ // Append the request handler, main query and format params.
+ $server_url .= '/select?q=[val]&wt=json';
+ $autocomplete_url = $autocomplete_url_value ? $autocomplete_url_value : $server_url;
+
+ // Default to the form values
+ $username = $values['autocomplete']['basic_auth']['username'];
+ $password = $values['autocomplete']['basic_auth']['password'];
+ $use_search_app_creds = $values['autocomplete']['basic_auth']['use_search_app_creds'];
+ // Add basic auth credentials
+ if ($use_search_app_creds) {
+ $username = $app_config->get('index.username');
+ $password = $app_config->get('index.password');
+ }
+
+ // Set the actual autocomplete config options.
+ $autocomplete['url'] = $autocomplete_url;
+ $autocomplete['use_search_app_creds'] = $use_search_app_creds;
+ if ($username) {
+ $autocomplete['username'] = $username;
+ }
+ if ($password) {
+ $autocomplete['password'] = $password;
+ }
+ if ($username && $password) {
+ $autocomplete['userpass'] = base64_encode($username . ':' . $password);
+ }
+ if ($values['autocomplete']['autocomplete_is_append_wildcard']) {
+ $autocomplete['appendWildcard'] = $values['autocomplete']['autocomplete_is_append_wildcard'];
+ }
+ if ($values['autocomplete']['autocomplete_suggestion_rows']) {
+ $autocomplete['suggestionRows'] = $values['autocomplete']['autocomplete_suggestion_rows'];
+ }
+ if ($values['autocomplete']['autocomplete_num_chars']) {
+ $autocomplete['numChars'] = $values['autocomplete']['autocomplete_num_chars'];
+ }
+ if ($autocomplete_mode) {
+ $autocomplete['mode'] = $autocomplete_mode;
+ if ($values['autocomplete']['autocomplete_mode_title_text']) {
+ $autocomplete[$autocomplete_mode]['titleText'] = $values['autocomplete']['autocomplete_mode_title_text'];
+ }
+ if ($values['autocomplete']['autocomplete_mode_hide_directions']) {
+ $autocomplete[$autocomplete_mode]['hideDirectionsText'] = $values['autocomplete']['autocomplete_mode_hide_directions'];
+ }
+ }
+ }
+
+ $this->configuration['autocomplete'] = $autocomplete;
+ }
}
diff --git a/src/Plugin/views/style/SolrSerializer.php b/src/Plugin/views/style/SolrSerializer.php
new file mode 100644
index 000000000..4f84979d5
--- /dev/null
+++ b/src/Plugin/views/style/SolrSerializer.php
@@ -0,0 +1,38 @@
+