mirror of
https://github.com/discourse/discourse.git
synced 2024-11-24 04:31:56 +08:00
Convert Autocomplete to use promises
This commit is contained in:
parent
6c983218b3
commit
02bab415bd
|
@ -5,9 +5,7 @@
|
||||||
**/
|
**/
|
||||||
$.fn.autocomplete = function(options) {
|
$.fn.autocomplete = function(options) {
|
||||||
|
|
||||||
var addInputSelectedItem, autocompleteOptions, closeAutocomplete, completeEnd, completeStart, completeTerm, div, height;
|
var autocompletePlugin = this;
|
||||||
var inputSelectedItems, isInput, markSelected, me, oldClose, renderAutocomplete, selectedOption, updateAutoComplete, vals;
|
|
||||||
var width, wrap, _this = this;
|
|
||||||
|
|
||||||
if (this.length === 0) return;
|
if (this.length === 0) return;
|
||||||
|
|
||||||
|
@ -15,27 +13,40 @@ $.fn.autocomplete = function(options) {
|
||||||
this.data("closeAutocomplete")();
|
this.data("closeAutocomplete")();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.length !== 1) {
|
if (this.length !== 1) {
|
||||||
alert("only supporting one matcher at the moment");
|
alert("only supporting one matcher at the moment");
|
||||||
}
|
}
|
||||||
autocompleteOptions = null;
|
|
||||||
selectedOption = null;
|
var wrap = null;
|
||||||
completeStart = null;
|
var autocompleteOptions = null;
|
||||||
completeEnd = null;
|
var selectedOption = null;
|
||||||
me = this;
|
var completeStart = null;
|
||||||
div = null;
|
var completeEnd = null;
|
||||||
|
var me = this;
|
||||||
|
var div = null;
|
||||||
|
|
||||||
// input is handled differently
|
// input is handled differently
|
||||||
isInput = this[0].tagName === "INPUT";
|
var isInput = this[0].tagName === "INPUT";
|
||||||
inputSelectedItems = [];
|
var inputSelectedItems = [];
|
||||||
|
|
||||||
addInputSelectedItem = function(item) {
|
|
||||||
var d, prev, transformed;
|
var closeAutocomplete = function() {
|
||||||
|
if (div) {
|
||||||
|
div.hide().remove();
|
||||||
|
}
|
||||||
|
div = null;
|
||||||
|
completeStart = null;
|
||||||
|
autocompleteOptions = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
var addInputSelectedItem = function(item) {
|
||||||
|
var transformed;
|
||||||
if (options.transformComplete) {
|
if (options.transformComplete) {
|
||||||
transformed = options.transformComplete(item);
|
transformed = options.transformComplete(item);
|
||||||
}
|
}
|
||||||
d = $("<div class='item'><span>" + (transformed || item) + "<a href='#'><i class='icon-remove'></i></a></span></div>");
|
var d = $("<div class='item'><span>" + (transformed || item) + "<a href='#'><i class='icon-remove'></i></a></span></div>");
|
||||||
prev = me.parent().find('.item:last');
|
var prev = me.parent().find('.item:last');
|
||||||
if (prev.length === 0) {
|
if (prev.length === 0) {
|
||||||
me.parent().prepend(d);
|
me.parent().prepend(d);
|
||||||
} else {
|
} else {
|
||||||
|
@ -55,14 +66,32 @@ $.fn.autocomplete = function(options) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var completeTerm = function(term) {
|
||||||
|
if (term) {
|
||||||
|
if (isInput) {
|
||||||
|
me.val("");
|
||||||
|
addInputSelectedItem(term);
|
||||||
|
} else {
|
||||||
|
if (options.transformComplete) {
|
||||||
|
term = options.transformComplete(term);
|
||||||
|
}
|
||||||
|
var text = me.val();
|
||||||
|
text = text.substring(0, completeStart) + (options.key || "") + term + ' ' + text.substring(completeEnd + 1, text.length);
|
||||||
|
me.val(text);
|
||||||
|
Discourse.Utilities.setCaretPosition(me[0], completeStart + 1 + term.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closeAutocomplete();
|
||||||
|
};
|
||||||
|
|
||||||
if (isInput) {
|
if (isInput) {
|
||||||
width = this.width();
|
var width = this.width();
|
||||||
height = this.height();
|
var height = this.height();
|
||||||
wrap = this.wrap("<div class='ac-wrap clearfix'/>").parent();
|
wrap = this.wrap("<div class='ac-wrap clearfix'/>").parent();
|
||||||
wrap.width(width);
|
wrap.width(width);
|
||||||
this.width(150);
|
this.width(150);
|
||||||
this.attr('name', this.attr('name') + "-renamed");
|
this.attr('name', this.attr('name') + "-renamed");
|
||||||
vals = this.val().split(",");
|
var vals = this.val().split(",");
|
||||||
vals.each(function(x) {
|
vals.each(function(x) {
|
||||||
if (x !== "") {
|
if (x !== "") {
|
||||||
if (options.reverseTransform) {
|
if (options.reverseTransform) {
|
||||||
|
@ -74,19 +103,18 @@ $.fn.autocomplete = function(options) {
|
||||||
this.val("");
|
this.val("");
|
||||||
completeStart = 0;
|
completeStart = 0;
|
||||||
wrap.click(function() {
|
wrap.click(function() {
|
||||||
_this.focus();
|
autocompletePlugin.focus();
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
markSelected = function() {
|
var markSelected = function() {
|
||||||
var links;
|
var links = div.find('li a');
|
||||||
links = div.find('li a');
|
|
||||||
links.removeClass('selected');
|
links.removeClass('selected');
|
||||||
return $(links[selectedOption]).addClass('selected');
|
return $(links[selectedOption]).addClass('selected');
|
||||||
};
|
};
|
||||||
|
|
||||||
renderAutocomplete = function() {
|
var renderAutocomplete = function() {
|
||||||
var borderTop, mePos, pos, ul;
|
var borderTop, mePos, pos, ul;
|
||||||
if (div) {
|
if (div) {
|
||||||
div.hide().remove();
|
div.hide().remove();
|
||||||
|
@ -130,7 +158,7 @@ $.fn.autocomplete = function(options) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
updateAutoComplete = function(r) {
|
var updateAutoComplete = function(r) {
|
||||||
if (completeStart === null) return;
|
if (completeStart === null) return;
|
||||||
|
|
||||||
autocompleteOptions = r;
|
autocompleteOptions = r;
|
||||||
|
@ -141,17 +169,9 @@ $.fn.autocomplete = function(options) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
closeAutocomplete = function() {
|
|
||||||
if (div) {
|
|
||||||
div.hide().remove();
|
|
||||||
}
|
|
||||||
div = null;
|
|
||||||
completeStart = null;
|
|
||||||
autocompleteOptions = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// chain to allow multiples
|
// chain to allow multiples
|
||||||
oldClose = me.data("closeAutocomplete");
|
var oldClose = me.data("closeAutocomplete");
|
||||||
me.data("closeAutocomplete", function() {
|
me.data("closeAutocomplete", function() {
|
||||||
if (oldClose) {
|
if (oldClose) {
|
||||||
oldClose();
|
oldClose();
|
||||||
|
@ -159,40 +179,17 @@ $.fn.autocomplete = function(options) {
|
||||||
return closeAutocomplete();
|
return closeAutocomplete();
|
||||||
});
|
});
|
||||||
|
|
||||||
completeTerm = function(term) {
|
|
||||||
var text;
|
|
||||||
if (term) {
|
|
||||||
if (isInput) {
|
|
||||||
me.val("");
|
|
||||||
addInputSelectedItem(term);
|
|
||||||
} else {
|
|
||||||
if (options.transformComplete) {
|
|
||||||
term = options.transformComplete(term);
|
|
||||||
}
|
|
||||||
text = me.val();
|
|
||||||
text = text.substring(0, completeStart) + (options.key || "") + term + ' ' + text.substring(completeEnd + 1, text.length);
|
|
||||||
me.val(text);
|
|
||||||
Discourse.Utilities.setCaretPosition(me[0], completeStart + 1 + term.length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return closeAutocomplete();
|
|
||||||
};
|
|
||||||
|
|
||||||
$(this).keypress(function(e) {
|
$(this).keypress(function(e) {
|
||||||
var caretPosition, prevChar, term;
|
if (!options.key) return;
|
||||||
if (!options.key) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
/* keep hunting backwards till you hit a
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
// keep hunting backwards till you hit a
|
||||||
if (e.which === options.key.charCodeAt(0)) {
|
if (e.which === options.key.charCodeAt(0)) {
|
||||||
caretPosition = Discourse.Utilities.caretPosition(me[0]);
|
var caretPosition = Discourse.Utilities.caretPosition(me[0]);
|
||||||
prevChar = me.val().charAt(caretPosition - 1);
|
var prevChar = me.val().charAt(caretPosition - 1);
|
||||||
if (!prevChar || /\s/.test(prevChar)) {
|
if (!prevChar || /\s/.test(prevChar)) {
|
||||||
completeStart = completeEnd = caretPosition;
|
completeStart = completeEnd = caretPosition;
|
||||||
term = "";
|
var term = "";
|
||||||
options.dataSource(term, updateAutoComplete);
|
options.dataSource(term).then(updateAutoComplete);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -202,9 +199,7 @@ $.fn.autocomplete = function(options) {
|
||||||
if (!options.key) {
|
if (!options.key) {
|
||||||
completeStart = 0;
|
completeStart = 0;
|
||||||
}
|
}
|
||||||
if (e.which === 16) {
|
if (e.which === 16) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ((completeStart === null) && e.which === 8 && options.key) {
|
if ((completeStart === null) && e.which === 8 && options.key) {
|
||||||
c = Discourse.Utilities.caretPosition(me[0]);
|
c = Discourse.Utilities.caretPosition(me[0]);
|
||||||
next = me[0].value[c];
|
next = me[0].value[c];
|
||||||
|
@ -222,13 +217,15 @@ $.fn.autocomplete = function(options) {
|
||||||
completeStart = c;
|
completeStart = c;
|
||||||
caretPosition = completeEnd = initial;
|
caretPosition = completeEnd = initial;
|
||||||
term = me[0].value.substring(c + 1, initial);
|
term = me[0].value.substring(c + 1, initial);
|
||||||
options.dataSource(term, updateAutoComplete);
|
options.dataSource(term).then(updateAutoComplete);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
prevIsGood = /[a-zA-Z\.]/.test(prev);
|
prevIsGood = /[a-zA-Z\.]/.test(prev);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ESC
|
||||||
if (e.which === 27) {
|
if (e.which === 27) {
|
||||||
if (completeStart !== null) {
|
if (completeStart !== null) {
|
||||||
closeAutocomplete();
|
closeAutocomplete();
|
||||||
|
@ -236,31 +233,26 @@ $.fn.autocomplete = function(options) {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (completeStart !== null) {
|
if (completeStart !== null) {
|
||||||
caretPosition = Discourse.Utilities.caretPosition(me[0]);
|
caretPosition = Discourse.Utilities.caretPosition(me[0]);
|
||||||
/* If we've backspaced past the beginning, cancel unless no key
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
// If we've backspaced past the beginning, cancel unless no key
|
||||||
if (caretPosition <= completeStart && options.key) {
|
if (caretPosition <= completeStart && options.key) {
|
||||||
closeAutocomplete();
|
closeAutocomplete();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
/* Keyboard codes! So 80's.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
// Keyboard codes! So 80's.
|
||||||
switch (e.which) {
|
switch (e.which) {
|
||||||
case 13:
|
case 13:
|
||||||
case 39:
|
case 39:
|
||||||
case 9:
|
case 9:
|
||||||
if (!autocompleteOptions) {
|
if (!autocompleteOptions) return true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (selectedOption >= 0 && (userToComplete = autocompleteOptions[selectedOption])) {
|
if (selectedOption >= 0 && (userToComplete = autocompleteOptions[selectedOption])) {
|
||||||
completeTerm(userToComplete);
|
completeTerm(userToComplete);
|
||||||
} else {
|
} else {
|
||||||
/* We're cancelling it, really.
|
// We're cancelling it, really.
|
||||||
*/
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
closeAutocomplete();
|
closeAutocomplete();
|
||||||
|
@ -284,9 +276,7 @@ $.fn.autocomplete = function(options) {
|
||||||
markSelected();
|
markSelected();
|
||||||
return false;
|
return false;
|
||||||
default:
|
default:
|
||||||
/* otherwise they're typing - let's search for it!
|
// otherwise they're typing - let's search for it!
|
||||||
*/
|
|
||||||
|
|
||||||
completeEnd = caretPosition;
|
completeEnd = caretPosition;
|
||||||
if (e.which === 8) {
|
if (e.which === 8) {
|
||||||
caretPosition--;
|
caretPosition--;
|
||||||
|
@ -309,7 +299,7 @@ $.fn.autocomplete = function(options) {
|
||||||
term += ",";
|
term += ",";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
options.dataSource(term, updateAutoComplete);
|
options.dataSource(term).then(updateAutoComplete);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
@method debounce
|
@method debounce
|
||||||
@module Discourse
|
@module Discourse
|
||||||
@param {function} func The function to debounce
|
@param {function} func The function to debounce
|
||||||
@param {Numbers} wait how long to wait
|
@param {Number} wait how long to wait
|
||||||
**/
|
**/
|
||||||
Discourse.debounce = function(func, wait) {
|
Discourse.debounce = function(func, wait) {
|
||||||
var timeout = null;
|
var timeout = null;
|
||||||
|
@ -36,3 +36,35 @@ Discourse.debounce = function(func, wait) {
|
||||||
return timeout;
|
return timeout;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Debounce a javascript function that returns a promise. If it's called too soon it
|
||||||
|
will return a promise that is never resolved.
|
||||||
|
|
||||||
|
@method debouncePromise
|
||||||
|
@module Discourse
|
||||||
|
@param {function} func The function to debounce
|
||||||
|
@param {Number} wait how long to wait
|
||||||
|
**/
|
||||||
|
Discourse.debouncePromise = function(func, wait) {
|
||||||
|
var timeout = null;
|
||||||
|
var args = null;
|
||||||
|
|
||||||
|
return function() {
|
||||||
|
var context = this;
|
||||||
|
var promise = Ember.Deferred.create();
|
||||||
|
args = arguments;
|
||||||
|
|
||||||
|
if (!timeout) {
|
||||||
|
timeout = Em.run.later(function () {
|
||||||
|
timeout = null;
|
||||||
|
func.apply(context, args).then(function (y) {
|
||||||
|
promise.resolve(y)
|
||||||
|
});
|
||||||
|
}, wait);
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
|
@ -9,40 +9,34 @@ var cache = {};
|
||||||
var cacheTopicId = null;
|
var cacheTopicId = null;
|
||||||
var cacheTime = null;
|
var cacheTime = null;
|
||||||
|
|
||||||
var doSearch = function(term, topicId, success) {
|
var debouncedSearch = Discourse.debouncePromise(function(term, topicId) {
|
||||||
return Discourse.ajax({
|
return Discourse.ajax({
|
||||||
url: Discourse.getURL('/users/search/users'),
|
url: Discourse.getURL('/users/search/users'),
|
||||||
dataType: 'JSON',
|
|
||||||
data: {
|
data: {
|
||||||
term: term,
|
term: term,
|
||||||
topic_id: topicId
|
topic_id: topicId
|
||||||
},
|
|
||||||
success: function(r) {
|
|
||||||
cache[term] = r;
|
|
||||||
cacheTime = new Date();
|
|
||||||
return success(r);
|
|
||||||
}
|
}
|
||||||
|
}).then(function (r) {
|
||||||
|
cache[term] = r;
|
||||||
|
cacheTime = new Date();
|
||||||
|
return r;
|
||||||
});
|
});
|
||||||
};
|
}, 200);
|
||||||
|
|
||||||
var debouncedSearch = Discourse.debounce(doSearch, 200);
|
|
||||||
|
|
||||||
Discourse.UserSearch = {
|
Discourse.UserSearch = {
|
||||||
|
|
||||||
search: function(options) {
|
search: function(options) {
|
||||||
var term = options.term || "";
|
var term = options.term || "";
|
||||||
var callback = options.callback;
|
|
||||||
var exclude = options.exclude || [];
|
var exclude = options.exclude || [];
|
||||||
var topicId = options.topicId;
|
var topicId = options.topicId;
|
||||||
var limit = options.limit || 5;
|
var limit = options.limit || 5;
|
||||||
if (!callback) {
|
|
||||||
throw "missing callback";
|
var promise = Ember.Deferred.create();
|
||||||
}
|
|
||||||
|
|
||||||
// TODO site setting for allowed regex in username
|
// TODO site setting for allowed regex in username
|
||||||
if (term.match(/[^a-zA-Z0-9\_\.]/)) {
|
if (term.match(/[^a-zA-Z0-9\_\.]/)) {
|
||||||
callback([]);
|
promise.resolve([]);
|
||||||
return true;
|
return promise;
|
||||||
}
|
}
|
||||||
if ((new Date() - cacheTime) > 30000) {
|
if ((new Date() - cacheTime) > 30000) {
|
||||||
cache = {};
|
cache = {};
|
||||||
|
@ -60,15 +54,15 @@ Discourse.UserSearch = {
|
||||||
if (result.length > limit) return false;
|
if (result.length > limit) return false;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
return callback(result);
|
promise.resolve(result);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (cache[term]) {
|
if (cache[term]) {
|
||||||
success(cache[term]);
|
success(cache[term]);
|
||||||
} else {
|
} else {
|
||||||
debouncedSearch(term, topicId, success);
|
debouncedSearch(term, topicId).then(success);
|
||||||
}
|
}
|
||||||
return true;
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -183,10 +183,9 @@ Discourse.ComposerView = Discourse.View.extend({
|
||||||
$wmdInput.data('init', true);
|
$wmdInput.data('init', true);
|
||||||
$wmdInput.autocomplete({
|
$wmdInput.autocomplete({
|
||||||
template: template,
|
template: template,
|
||||||
dataSource: function(term, callback) {
|
dataSource: function(term) {
|
||||||
return Discourse.UserSearch.search({
|
return Discourse.UserSearch.search({
|
||||||
term: term,
|
term: term,
|
||||||
callback: callback,
|
|
||||||
topicId: _this.get('controller.controllers.topic.content.id')
|
topicId: _this.get('controller.controllers.topic.content.id')
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -198,13 +197,14 @@ Discourse.ComposerView = Discourse.View.extend({
|
||||||
$('#private-message-users').val(this.get('content.targetUsernames')).autocomplete({
|
$('#private-message-users').val(this.get('content.targetUsernames')).autocomplete({
|
||||||
template: template,
|
template: template,
|
||||||
|
|
||||||
dataSource: function(term, callback) {
|
dataSource: function(term) {
|
||||||
return Discourse.UserSearch.search({
|
return Discourse.UserSearch.search({
|
||||||
term: term,
|
term: term,
|
||||||
callback: callback,
|
topicId: _this.get('controller.controllers.topic.content.id'),
|
||||||
exclude: selected.concat([Discourse.get('currentUser.username')])
|
exclude: selected.concat([Discourse.get('currentUser.username')])
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onChangeItems: function(items) {
|
onChangeItems: function(items) {
|
||||||
items = $.map(items, function(i) {
|
items = $.map(items, function(i) {
|
||||||
if (i.username) {
|
if (i.username) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user