Commit 8e49b7db authored by Ryan Herbert's avatar Ryan Herbert

Replace jquery.autocomplete.js with jquery.atwho.js

Although the jquery plugin autocomplete.js was providing the basic
functionnality we needed, it was not natively able to display the list
of suggestions in the desired position for our textareas.

At.js is capable through the use of Caret.js of displaying a suggestion
box under the caret in a textarea.

This javascript code is heavily inspired by the code present in the
gitlab-ce project and allows us to have a nicely cached data-set to
limit database queries.
parent 016b984a
......@@ -2118,4 +2118,79 @@ label.highlight_label {
.widestBox {
min-width: @width_axisBox;
}
\ No newline at end of file
}
/* atwho css */
.atwho-view {
position:absolute;
top: 0;
left: 0;
display: none;
margin-top: 18px;
background: white;
color: black;
border: 1px solid #DDD;
border-radius: 3px;
box-shadow: 0 0 5px rgba(0,0,0,0.1);
min-width: 120px;
z-index: 11110 !important;
}
.atwho-view .atwho-header {
padding: 5px;
margin: 5px;
cursor: pointer;
border-bottom: solid 1px #eaeff1;
color: #6f8092;
font-size: 11px;
font-weight: bold;
}
.atwho-view .atwho-header .small {
color: #6f8092;
float: right;
padding-top: 2px;
margin-right: -5px;
font-size: 12px;
font-weight: normal;
}
.atwho-view .atwho-header:hover {
cursor: default;
}
.atwho-view .cur {
background: #3366FF;
color: white;
}
.atwho-view .cur small {
color: white;
}
.atwho-view strong {
color: #3366FF;
}
.atwho-view .cur strong {
color: white;
font:bold;
}
.atwho-view ul {
/* width: 100px; */
list-style:none;
padding:0;
margin:auto;
max-height: 200px;
overflow-y: auto;
}
.atwho-view ul li {
display: block;
padding: 5px 10px;
border-bottom: 1px solid #DDD;
cursor: pointer;
/* border-top: 1px solid #C8C8C8; */
}
.atwho-view small {
font-size: smaller;
color: #777;
font-weight: normal;
}
......@@ -13,7 +13,8 @@ require(["jquery",
"file",
"tsne",
"jstree.min",
"jquery.autocomplete"], function() {
"jquery.caret",
"jquery.atwho"], function() {
// Then config file (needed by Vidjil)
require(['../conf'], function() {
loadAfterConf()
......
......@@ -20,13 +20,147 @@
* along with "Vidjil". If not, see <http://www.gnu.org/licenses/>
*/
// This code is heavily inspired by the code produced for Gitlab's GfmAutoComplete
function VidjilAutoComplete(datasource) {
if (typeof VidjilAutoComplete.instance === 'object') {
return VidjilAutoComplete.instance;
}
this.datasource = datasource;
this.isLoadingData = {};
this.cachedData = {};
this.loadedGroup = -1;
VidjilAutoComplete.instance = this;
return this;
}
// static methods
VidjilAutoComplete.defaultLoadingData = ['loading'];
VidjilAutoComplete.isLoading = function(data) {
dataToInspect = data;
if (data && data.length > 0) {
dataToInspect = data[0];
}
var loadingState = VidjilAutoComplete.defaultLoadingData[0];
return dataToInspect && (dataToInspect === loadingState || dataToInspect.name === loadingState);
}
// instance methods
VidjilAutoComplete.prototype = {
initCache : function(at) {
this.cachedData[at] = {};
},
clearCache : function() {
this.cachedData = {};
},
isLoaded : function(group_id) {
return this.loadedGroup == group_id;
},
setupAtWho: function(input) {
const $input = $(input);
if ($input.data('needs-atwho')) {
$input.off('focus.setupAtWho').on('focus.setupAtWho', this.setupTags.bind(this, $input));
$input.on('change.atwho', () => input.dispatchEvent(new Event('input')));
// This triggers at.js again
// Needed for quick actions with suffixes (ex: /label ~)
$input.on('inserted-commands.atwho', $input.trigger.bind($input, 'keyup'));
$input.on('clear-commands-cache.atwho', () => this.clearCache());
$input.data('needs-atwho', false);
// hackiness to get the autocompletion ready right away
$input.trigger('focus.setupAtWho');
}
},
setupTags : function($input) {
console.log("setup");
var self = this;
var at = '#';
this.initCache(at);
$input.atwho({
at: at,
alias: 'tags',
data: VidjilAutoComplete.defaultLoadingData,
callbacks: {
...self.getDefaultCallbacks()
},
searchKey: 'search',
});
},
getDefaultCallbacks : function() {
const fetchData = this.fetchData.bind(this);
const isLoaded = this.isLoaded.bind(this);
var callbacks = {
filter : function(query, data, searchKey) {
var group_id = this.$inputor.data('group-id');
if (VidjilAutoComplete.isLoading(data) || !isLoaded(group_id)) {
this.$inputor.atwho('load', this.at, VidjilAutoComplete.defaultLoadingData);
fetchData(this.$inputor, this.at, group_id);
return data;
}
return $.fn.atwho.default.callbacks.filter(query, data, searchKey);
},
beforeSave(tags) {
return $.map(tags, (i) => {
if (i.name == null) {
return i;
}
return {
id: i.id,
name: i.name,
search: `${i.id} ${i.name}`,
};
});
},
}
return callbacks;
},
fetchData : function($input, at, group_id) {
var self = this;
if (this.isLoadingData[at]) return;
this.isLoadingData[at] = true;
if (this.cachedData[at][group_id]) {
this.loadData($input, at, this.cachedData[at][group_id], group_id);
} else {
$.ajax({
type: "GET",
data: {
group_id: group_id
},
timeout: 5000,
crossDomain: true,
url: self.datasource,
success: function (data) {
var my_data = JSON.parse(data);
self.loadData($input, at, my_data, group_id);
},
error: function (request, status, error) {
self.isLoadingData[at] = false;
}
});
}
},
loadData : function($input, at, data, group_id) {
this.isLoadingData[at] = false;
this.cachedData[at][group_id] = data;
this.loadedGroup = group_id;
$input.atwho('load', at, data);
// This trigger at.js again
// otherwise we would be stuck with loading until the user types
return $input.trigger('keyup');
},
function init_autocomplete(elem, group_id) {
var service = 'tag/auto_complete';
var address = db.db_address + service;
$(elem).autocomplete({'serviceUrl': address,
'dataType': 'json',
'params': {'group_id': group_id},
'delimiter': /[,\.-_=+()$%^&*!@\[\]\{\}\"|'?\\\/><\s]/
});
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -87,6 +87,8 @@ try {
//TODO
initMenu();
new VidjilAutoComplete(db.db_address + 'tag/auto_complete');
} catch(err) {
this.db.log_error(err)
}
......
......@@ -10,10 +10,7 @@ def auto_complete():
if "group_id" not in request.vars:
return error_message("missing group id")
if "query" not in request.vars or request.vars["query"][0] != "#":
tags = []
else:
prefix = get_tag_prefix()
tags = get_tags(db, request.vars["group_id"], request.vars["query"][len(prefix):])
prefix = get_tag_prefix()
tags = get_tags(db, request.vars["group_id"])
return tags_to_json(tags)
......@@ -87,11 +87,9 @@ def register_tags(db, table, record_id, text, group_id, reset=False):
tag_extractor = TagExtractor(tag_prefix, db)
tags = tag_extractor.execute(table, record_id, text, group_id, reset)
def get_tags(db, group_id, query):
like = '%s%%' % query
def get_tags(db, group_id):
return db((db.tag.id == db.group_tag.tag_id) &
(db.group_tag.group_id == group_id) &
(db.tag.name.like(like, case_sensitive=False))
(db.group_tag.group_id == group_id)
).select(db.tag.ALL)
def tags_to_json(tags):
......@@ -100,8 +98,7 @@ def tags_to_json(tags):
for tag in tags:
tag_dict = {}
tag_dict['id'] = tag.id
tag_dict['value'] = '%s%s' % (prefix, tag.name)
tag_dict['name'] = tag.name
tag_list.append(tag_dict)
suggestions = {'suggestions': tag_list}
return json.dumps(suggestions)
return json.dumps(tag_list)
......@@ -32,7 +32,7 @@
</tr>
<tr>
<td><label for="patient_info" id="patient_info__label">Info: </label></td>
<td><textarea onfocus="init_autocomplete(this, $('#group_select option:selected').val());" class="text" cols="40" id="patient_info" name="info" rows="10"></textarea></td>
<td><textarea onfocus="$(this).data('group-id', $('#group_select option:selected').val()); new VidjilAutoComplete().setupAtWho(this);" data-needs-atwho="true" class="text" cols="40" id="patient_info" name="info" rows="10"></textarea></td>
<td></td>
</tr>
<tr>
......
......@@ -30,7 +30,7 @@ info = db.patient[request.vars["id"]]
</tr>
<tr>
<td> <label for="patient_info" id="patient_info__label">Info: </label> </td>
<td> <textarea onfocus="init_autocomplete(this, {{=group_id}});" class="text" cols="40" id="patient_info" name="info" rows="10">{{=info.info}}</textarea> </td>
<td> <textarea onfocus="new VidjilAutoComplete().setupAtWho(this);" data-needs-atwho="true" data-group-id="{{=group_id}}" class="text" cols="40" id="patient_info" name="info" rows="10">{{=info.info}}</textarea> </td>
<td></td>
</tr>
<tr>
......
......@@ -27,7 +27,7 @@
</tr>
<tr>
<td><label for="run_info" id="run_info__label">Info: </label></td>
<td><textarea onfocus="init_autocomplete(this, $('#group_select option:selected').val());" class="text" cols="60" id="run_info" name="info" rows="10"></textarea></td>
<td><textarea onfocus="$(this).data('group-id', $('#group_select option:selected').val()); new VidjilAutoComplete().setupAtWho(this);" data-needs-atwho="true" class="text" cols="40" id="run_info" name="info" rows="10"></textarea></td>
<td></td>
</tr>
<tr>
......
......@@ -25,7 +25,7 @@ info = db.run[request.vars["id"]]
</tr>
<tr>
<td> <label for="run_info" id="run_info__label">Info: </label> </td>
<td> <textarea onfocus="init_autocomplete(this, {{=group_id}});" class="text" cols="60" id="run_info" name="info" rows="10">{{=info.info}}</textarea> </td>
<td> <textarea onfocus="new VidjilAutoComplete().setupAtWho(this);" data-needs-atwho="true" data-group-id="{{=group_id}}" class="text" cols="40" id="run_info" name="info" rows="10">{{=info.info}}</textarea> </td>
<td></td>
</tr>
<tr>
......
......@@ -17,7 +17,7 @@
</tr>
<tr>
<td><label for="sample_set_info" id="sample_set_info__label">Info: </label></td>
<td><textarea onfocus="init_autocomplete(this, $('#group_select option:selected').val());" class="text" cols="60" id="sample_set_info" name="info" rows="10"></textarea></td>
<td><textarea onfocus="$(this).data('group-id', $('#group_select option:selected').val()); new VidjilAutoComplete().setupAtWho(this);" data-needs-atwho="true" class="text" cols="40" id="sample_set_info" name="info" rows="10"></textarea></td>
<td></td>
</tr>
<tr>
......
......@@ -15,7 +15,7 @@ info = db.generic[request.vars["id"]]
</tr>
<tr>
<td> <label for="sample_set_info" id="sample_set_info__label">Info: </label> </td>
<td> <textarea onfocus="init_autocomplete(this, {{=group_id}});" class="text" cols="60" id="sample_set_info" name="info" rows="10">{{=info.info}}</textarea> </td>
<td> <textarea onfocus="new VidjilAutoComplete().setupAtWho(this);" data-needs-atwho="true" data-group-id="{{=group_id}}" class="text" cols="40" id="sample_set_info" name="info" rows="10">{{=info.info}}</textarea> </td>
<td></td>
</tr>
<tr>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment