1
0
mirror of https://github.com/phpbb/phpbb.git synced 2025-07-28 12:30:42 +02:00

Merge pull request #2267 from prototech/ticket/10737

[ticket/10737] Add  live member search.

* prototech/ticket/10737:
  [ticket/10737] Remove loading indicator.
  [ticket/10737] Enforce allow_live_searches setting in memberlist.php.
  [ticket/10737] Add config setting to disable live searches.
  [ticket/10737] Add loading indicator and alert box code to simple_footer.html.
  [ticket/10737] Load core.js and ajax.js in simple_footer.html.
  [ticket/10737] Set the username as the input value instead of redirecting.
  [ticket/10737] Drop subsilver2 changes.
  [ticket/10737] Add a more generic live search implementation.
  [ticket/10737] Clean up memberlist.php.
  [ticket/10737] Use dropdown for search results container.
  [ticket/10737] Adding delayed keyup and removing target_blank.
  [ticket/10737] Using UTF-8 aware alternatives in PHP code.
  [ticket/10737] Removing obsolete code.
  [ticket/10737] Avoid hard-coding table row and use case-insensitive search.
  [ticket/10737] Removing unnecessary/obsolete code.
  [ticket/10737] Using JQuery events and JSON response.
  [ticket/10737] Code fixes in AJAX search feature
  [ticket/10737] Improvements over last commit
  [ticket/10737] Adding username suggestions in "Find a member" using AJAX
This commit is contained in:
Joas Schilling
2014-05-03 16:39:31 +02:00
12 changed files with 438 additions and 21 deletions

View File

@@ -249,7 +249,16 @@ phpbb.ajaxify = function(options) {
callback = options.callback,
overlay = (typeof options.overlay !== 'undefined') ? options.overlay : true,
isForm = elements.is('form'),
eventName = isForm ? 'submit' : 'click';
isText = elements.is('input[type="text"], textarea'),
eventName;
if (isForm) {
eventName = 'submit';
} else if (isText) {
eventName = 'keyup';
} else {
eventName = 'click';
}
elements.bind(eventName, function(event) {
var action, method, data, submit, that = this, $this = $(this);
@@ -349,6 +358,7 @@ phpbb.ajaxify = function(options) {
// If the element is a form, POST must be used and some extra data must
// be taken from the form.
var runFilter = (typeof options.filter === 'function');
var data = {};
if (isForm) {
action = $this.attr('action').replace('&', '&');
@@ -362,33 +372,41 @@ phpbb.ajaxify = function(options) {
value: submit.val()
});
}
} else if (isText) {
var name = ($this.attr('data-name') !== undefined) ? $this.attr('data-name') : this['name'];
action = $this.attr('data-url').replace('&', '&');
data[name] = this.value;
method = 'POST';
} else {
action = this.href;
data = null;
method = 'GET';
}
var sendRequest = function() {
if (overlay && (typeof $this.attr('data-overlay') === 'undefined' || $this.attr('data-overlay') === 'true')) {
phpbb.loadingIndicator();
}
var request = $.ajax({
url: action,
type: method,
data: data,
success: returnHandler,
error: errorHandler
});
request.always(function() {
loadingIndicator.fadeOut(phpbb.alertTime);
});
};
// If filter function returns false, cancel the AJAX functionality,
// and return true (meaning that the HTTP request will be sent normally).
if (runFilter && !options.filter.call(this, data)) {
if (runFilter && !options.filter.call(this, data, event, sendRequest)) {
return;
}
if (overlay && (typeof $this.attr('data-overlay') === 'undefined' || $this.attr('data-overlay') === 'true')) {
phpbb.loadingIndicator();
}
var request = $.ajax({
url: action,
type: method,
data: data,
success: returnHandler,
error: errorHandler
});
request.always(function() {
loadingIndicator.fadeOut(phpbb.alertTime);
});
sendRequest();
event.preventDefault();
});
@@ -404,6 +422,278 @@ phpbb.ajaxify = function(options) {
return this;
};
phpbb.search = {cache: {data: []}, tpl: [], container: []};
/**
* Get cached search data.
*
* @param string id Search ID.
* @return bool|object. Cached data object. Returns false if no data exists.
*/
phpbb.search.cache.get = function(id) {
if (this.data[id]) {
return this.data[id];
}
return false;
};
/**
* Set search cache data value.
*
* @param string id Search ID.
* @param string key Data key.
* @param string value Data value.
*
* @return undefined
*/
phpbb.search.cache.set = function(id, key, value) {
if (!this.data[id]) {
this.data[id] = {results: []};
}
this.data[id][key] = value;
};
/**
* Cache search result.
*
* @param string id Search ID.
* @param string keyword Keyword.
* @param array results Search results.
*
* @return undefined
*/
phpbb.search.cache.setResults = function(id, keyword, value) {
this.data[id]['results'][keyword] = value;
};
/**
* Trim spaces from keyword and lower its case.
*
* @param string keyword Search keyword to clean.
* @return string Cleaned string.
*/
phpbb.search.cleanKeyword = function(keyword) {
return $.trim(keyword).toLowerCase();
};
/**
* Get clean version of search keyword. If textarea supports several keywords
* (one per line), it fetches the current keyword based on the caret position.
*
* @param jQuery el Search input|textarea.
* @param string keyword Input|textarea value.
* @param bool multiline Whether textarea supports multiple search keywords.
*
* @return string Clean string.
*/
phpbb.search.getKeyword = function(el, keyword, multiline) {
if (multiline) {
var line = phpbb.search.getKeywordLine(el);
keyword = keyword.split("\n").splice(line, 1);
}
return phpbb.search.cleanKeyword(keyword);
};
/**
* Get the textarea line number on which the keyword resides - for textareas
* that support multiple keywords (one per line).
*
* @param jQuery el Search textarea.
* @return int
*/
phpbb.search.getKeywordLine = function (el) {
return el.val().substr(0, el.get(0).selectionStart).split("\n").length - 1;
};
/**
* Set the value on the input|textarea. If textarea supports multiple
* keywords, only the active keyword is replaced.
*
* @param jQuery el Search input|textarea.
* @param string value Value to set.
* @param bool multiline Whether textarea supports multiple search keywords.
*
* @return undefined
*/
phpbb.search.setValue = function(el, value, multiline) {
if (multiline) {
var line = phpbb.search.getKeywordLine(el),
lines = el.val().split("\n");
lines[line] = value;
value = lines.join("\n");
}
el.val(value);
};
/**
* Sets the onclick event to set the value on the input|textarea to the selected search result.
*
* @param jQuery el Search input|textarea.
* @param object value Result object.
* @param object container jQuery object for the search container.
*
* @return undefined
*/
phpbb.search.setValueOnClick = function(el, value, row, container) {
row.click(function() {
phpbb.search.setValue(el, value.result, el.attr('data-multiline'));
container.hide();
});
};
/**
* Runs before the AJAX search request is sent and determines whether
* there is a need to contact the server. If there are cached results
* already, those are displayed instead. Executes the AJAX request function
* itself due to the need to use a timeout to limit the number of requests.
*
* @param array data Data to be sent to the server.
* @param object event Onkeyup event object.
* @param function sendRequest Function to execute AJAX request.
*
* @return bool Returns false.
*/
phpbb.search.filter = function(data, event, sendRequest) {
var el = $(this),
dataName = (el.attr('data-name') !== undefined) ? el.attr('data-name') : el.attr('name'),
minLength = parseInt(el.attr('data-min-length')),
searchID = el.attr('data-results'),
keyword = phpbb.search.getKeyword(el, data[dataName], el.attr('data-multiline')),
cache = phpbb.search.cache.get(searchID),
proceed = true;
data[dataName] = keyword;
if (cache['timeout']) {
clearTimeout(cache['timeout']);
}
var timeout = setTimeout(function() {
// Check min length and existence of cache.
if (minLength > keyword.length) {
proceed = false;
} else if (cache['last_search']) {
// Has the keyword actually changed?
if (cache['last_search'] === keyword) {
proceed = false;
} else {
// Do we already have results for this?
if (cache['results'][keyword]) {
var response = {keyword: keyword, results: cache['results'][keyword]};
phpbb.search.handleResponse(response, el, true);
proceed = false;
}
// If the previous search didn't yield results and the string only had characters added to it,
// then we won't bother sending a request.
if (keyword.indexOf(cache['last_search']) === 0 && cache['results'][cache['last_search']].length === 0) {
phpbb.search.cache.set(searchID, 'last_search', keyword);
phpbb.search.cache.setResults(searchID, keyword, []);
proceed = false;
}
}
}
if (proceed) {
sendRequest.call(this);
}
}, 350);
phpbb.search.cache.set(searchID, 'timeout', timeout);
return false;
};
/**
* Handle search result response.
*
* @param object res Data received from server.
* @param jQuery el Search input|textarea.
* @param bool fromCache Whether the results are from the cache.
* @param function callback Optional callback to run when assigning each search result.
*
* @return undefined
*/
phpbb.search.handleResponse = function(res, el, fromCache, callback) {
if (typeof res !== 'object') {
return;
}
var searchID = el.attr('data-results'),
container = $(searchID);
if (this.cache.get(searchID)['callback']) {
callback = this.cache.get(searchID)['callback'];
} else if (typeof callback === 'function') {
this.cache.set(searchID, 'callback', callback);
}
if (!fromCache) {
this.cache.setResults(searchID, res.keyword, res.results);
}
this.cache.set(searchID, 'last_search', res.keyword);
this.showResults(res.results, el, container, callback);
};
/**
* Show search results.
*
* @param array results Search results.
* @param jQuery el Search input|textarea.
* @param jQuery container Search results container element.
* @param function callback Optional callback to run when assigning each search result.
*
* @return undefined
*/
phpbb.search.showResults = function(results, el, container, callback) {
var resultContainer = $('.search-results', container);
this.clearResults(resultContainer);
if (!results.length) {
container.hide();
return;
}
var searchID = container.attr('id'),
tpl,
row;
if (!this.tpl[searchID]) {
tpl = $('.search-result-tpl', container);
this.tpl[searchID] = tpl.clone().removeClass('search-result-tpl');
tpl.remove();
}
tpl = this.tpl[searchID];
$.each(results, function(i, item) {
row = tpl.clone();
row.find('.search-result').html(item.display);
if (typeof callback === 'function') {
callback.call(this, el, item, row, container);
}
row.appendTo(resultContainer).show();
});
container.show();
};
/**
* Clear search results.
*
* @param jQuery container Search results container.
* @return undefined
*/
phpbb.search.clearResults = function(container) {
container.children(':not(.search-result-tpl)').remove();
};
$('#phpbb').click(function(e) {
var target = $(e.target);
if (!target.is('.live-search') && !target.parents().is('.live-search')) {
$('.live-search').hide();
}
});
/**
* Hide the optgroups that are not the selected timezone
*
@@ -543,6 +833,12 @@ phpbb.addAjaxCallback = function(id, callback) {
return this;
};
/**
* This callback handles live member searches.
*/
phpbb.addAjaxCallback('member_search', function(res) {
phpbb.search.handleResponse(res, $(this), false, phpbb.getFunctionByName('phpbb.search.setValueOnClick'));
});
/**
* This callback alternates text - it replaces the current text with the text in
@@ -1111,6 +1407,24 @@ phpbb.toggleDisplay = function(id, action, type) {
$('#' + id).css('display', ((action === 1) ? type : 'none'));
}
/**
* Get function from name.
* Based on http://stackoverflow.com/a/359910
*
* @param string functionName Function to get.
* @return function
*/
phpbb.getFunctionByName = function (functionName) {
var namespaces = functionName.split('.'),
func = namespaces.pop(),
context = window;
for (var i = 0; i < namespaces.length; i++) {
context = context[namespaces[i]];
}
return context[func];
};
/**
* Apply code editor to all textarea elements with data-bbcode attribute
*/