Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mausolos added contextual filter functionality to autocomplete.inc.js… #769

Open
wants to merge 1 commit into
base: 7.x-1.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
This article covers both the general concept of using Autocomplete to access Views that have contextual filters, as well as the more advanced strategy of specifically targeting views that ALSO use SearchAPI's entity indexing capabilities.

If all you are trying to do is use the contextual filters, you can substitute a standard contextual filter for the more advanced Fulltext filter.

Using Views Search with SearchAPI's indexing is a powerful way to access entities via autocomplete quickly and with a high level of customization.

Here is an example of an autocomplete module that utilizes this feature. In this case, we're targeting the User entity.

To do this, you'll need to have a ![viable SearchAPI index set up for the target entity](https://www.drupal.org/node/1251376).

The view that we use for this example was set up much in the same way, except that we used a SearchAPI user index configured as follows:

- path is 'drupalgap/views_datasource/drupalgap_users/%'
- Format: JSON data document (settings are default provided)
- contextual filter settings:
- Search: Fulltext search filter
- Provide default argument; raw value from URL (1)
- Specify validation criteria (basic validation, hide/404 if not found)
- Searched fields: Extension, Firstname, Lastname, Name (the first 3 are custom fields we added, the last one is the default Drupal username)
- Operator: contains all these words
- Fields (all indexed user fields):
- Firstname
- Lastname
- name
- extention

Next, you'll need to ![set up a view that specifically utilizes the entity index](https://www.drupal.org/node/1597930).

Here's our custom autocomplete module example:

```
/**
* Implements hook_menu().
*/
function my_autocomplete_menu() {
var items = {};
items['my_autocomplete'] = {
title: 'Autocomplete',
page_callback: 'my_autocomplete_page'
};
return items;
}

/**
* Page callback for autocomplete input.
*/
function my_autocomplete_page() {
try {
var content = {};
content.my_autocomplete = {
theme: 'autocomplete',
item_onclick: 'my_autocomplete_item_onclick',
remote: true,
// basic path - while your view might be 'drupalgap/views_datasource/drupalgap_users/%', don't put the % here.
path: 'drupalgap/views_datasource/drupalgap_users',
// you need to specify the value, label and filter as zero-length strings, or things will break.
value: '',
label: '',
filter: '',
// you don't need these two settings here commented out, just don't use them at all.
// custom: 'false',
// handler: 'views'

/* what we added to make contextual filters work */
custom_theme: true,
// if none are specified or "custom_theme" is not set to true, default is array( 'value', 'label' ). must be in order.
theme_fields: [
'name',
'field_firstname',
'field_lastname',
'field_direct_office_phone',
'uid'
],
// this is your themable output string. this is what will show up in the result list. Note that you need to wrap your fieldnames in double curly braces.
theme_map: l('{{field_firstname}} {{field_lastname}} ({{name}}): #{{field_direct_office_phone}}', 'user/{{uid}}'),
// default behavior for filter_type is 'exposed', but the code won't look for 'exposed' necessarily
filter_type: 'contextual',
// if for some reason your view requires a specific URL structure (like, ~/search/users/%), you can describe this specifically in contextual_arg_structure.
// for example: ['search','users','%'];
contextual_arg_structure: ['%']
};
return content;
}
catch (error) { console.log('my_autocomplete_page - ' + error); }
}

function my_autocomplete_item_onclick(id, item) {
console.log('List id: ' + id);
drupalgap_alert("This will send to user path with ID of: " + $(item).attr('value'));
}
```

Note that even if your Drupal view contains more fields to display on result, the fields won't display in the Drupalgap result page unless you include them specifically in theme_fields and theme_map (though you can see them).

Likewise, here's some more complicated behavior you might not expect. Let's say you have another field specified, say "field_building". Let's say the building name is "Metropolis".

If you don't have "field_building" called out in theme_fields and theme_map and you type "metr" into the autocomplete field:

- you will be able to see objects that return as expected via console.log()
- you will not see those same objects listed in the result set UNLESS they happen to have "metr" in their other fields that are called out in theme_fields and theme_map
- you're not crazy if this happens, you just need to add them to theme_fields and theme_map
- if you don't want to actually SEE the Building field displayed but you still want the corresponding results to display, you can just use CSS and HTML to hide it, for example:

```
theme_map: l('{{field_firstname}} {{field_lastname}} ({{name}}): #{{field_direct_office_phone}} <span class="hidden">field_building</span>', 'user/{{uid}}'),
```

Authored by ![mausolos](https://www.drupal.org/u/mausolos)
4 changes: 4 additions & 0 deletions docs/13_Forms/03_Form_Elements/Autocomplete/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ var fruits = [
];
```

You can also if you have need to use contextual filters or have multiple fields that you are querying (for example, if you are using a contextual filter with full text search to query multiple fields).

See Autocomplete with Remote Views and Contextual Filters for details.

## Accessing the Input Values

The Autocomplete uses a hidden input to hold onto the value, that way the label can be shown in the text field.
Expand Down
95 changes: 67 additions & 28 deletions src/includes/autocomplete.inc.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ function theme_autocomplete(variables) {
function _theme_autocomplete(list, e, data, autocomplete_id) {
try {
var autocomplete = _theme_autocomplete_variables[autocomplete_id];
// Make sure a filter is present.
// Make sure a filter is present UNLESS a contextual filter is being used. {{CES}}
if (typeof autocomplete.filter === 'undefined') {
console.log(
'_theme_autocomplete - A "filter" was not supplied.'
Expand Down Expand Up @@ -196,34 +196,53 @@ function _theme_autocomplete(list, e, data, autocomplete_id) {

// Convert the result into an items array for a list. Each item will
// be a JSON object with a "value" and "label" properties.
// The above is true only if autocomplete.apply_post_filter is true (default)
var items = [];
var _value = autocomplete.value;
var _label = autocomplete.label;
for (var index in result_items) {
if (!autocomplete.custom_theme) { // added {{CES}}
var _value = autocomplete.value;
var _label = autocomplete.label;
for (var index in result_items) {
if (!result_items.hasOwnProperty(index)) { continue; }
var object = result_items[index];
var _item = null;
if (_wrapped) { _item = object[_child]; }
else { _item = object; }
var item = {
value: _item[_value],
label: _item[_label]
};
items.push(item);
}
// Now render the items, add them to list and refresh the list.
if (items.length != 0) {
autocomplete.items = items;
var _items = _theme_autocomplete_prepare_items(autocomplete);
for (var index in _items) {
if (!_items.hasOwnProperty(index)) { continue; }
var item = _items[index];
html += '<li>' + item + '</li>';
}
$ul.html(html);
$ul.listview('refresh');
$ul.trigger('updatelayout');
}
} else { // added {CES}
for (var index in result_items) {
if (!result_items.hasOwnProperty(index)) { continue; }
var object = result_items[index];
var _item = null;
if (_wrapped) { _item = object[_child]; }
else { _item = object; }
var item = {
value: _item[_value],
label: _item[_label]
};
items.push(item);
}

// Now render the items, add them to list and refresh the list.
if (items.length != 0) {
autocomplete.items = items;
var _items = _theme_autocomplete_prepare_items(autocomplete);
for (var index in _items) {
if (!_items.hasOwnProperty(index)) { continue; }
var item = _items[index];
html += '<li>' + item + '</li>';
var output_str = autocomplete.theme_map;
for (var i = 0; i < autocomplete.theme_fields.length; i++) {
var pattern = new RegExp("{{" + autocomplete.theme_fields[i] + "}}");
output_str = output_str.replace(pattern, _item[autocomplete.theme_fields[i]]);
}
html += '<li>' + output_str + '</li>';
$ul.html(html);
$ul.listview('refresh');
$ul.trigger('updatelayout');
}
$ul.html(html);
$ul.listview('refresh');
$ul.trigger('updatelayout');
}
}

Expand Down Expand Up @@ -260,12 +279,32 @@ function _theme_autocomplete(list, e, data, autocomplete_id) {

// Views (and Organic Groups)
case 'views':
// Prepare the path to the view.
var path = autocomplete.path + '?' + autocomplete.filter + '=' +
encodeURIComponent(value);
// Any extra params to send along?
if (autocomplete.params) { path += '&' + autocomplete.params; }

// Check to see if we need to target an exposed filter/alias. {{CES}}
var contextual_content = '';
if (autocomplete.filter_type == 'contextual') {
for (var i = 0; i < autocomplete.contextual_arg_structure.length; i++) {
if (autocomplete.contextual_arg_structure[i] != '%') {
contextual_content += '/' + autocomplete.contextual_arg_structure[i];
} else {
contextual_content += '/' + value;
}
}
}

// Override exposed filters if contextual filter content is available. {{CES}}
if (contextual_content.length > 0) {
autocomplete.filter = ''; // this line might not be necessary
// Prepare the path to the view.
var path = autocomplete.path + contextual_content;
} else {
// Prepare the path to the view.
var path = autocomplete.path + contextual_content + '?' + autocomplete.filter + '='
+ encodeURIComponent(value);

// Any extra params to send along?
if (autocomplete.params && contextual_content.length == 0) { path += '&' + autocomplete.params; }
}

// Retrieve JSON results. Keep in mind, we use this for retrieving
// Views JSON results and custom hook_menu() path results in Drupal.
views_datasource_get_view_result(path, {
Expand Down