Commit 000b6d3c authored by Marc Egger's avatar Marc Egger
Browse files

Merge branch 'marcTagsTypeahead' into 'develop'

Marc tags typeahead

See merge request !253
parents 069e3296 a9953016
Pipeline #3247 passed with stages
in 3 minutes and 35 seconds
This diff is collapsed.
......@@ -25,9 +25,11 @@ var QfqNS = QfqNS || {};
n.TypeAhead.install = function (typeahead_endpoint) {
$('.qfq-typeahead').each(function () {
var $shadowElement, bloodhoundConfiguration;
var bloodhoundConfiguration;
var $element = $(this);
// bloodhound is used to get the remote data (suggestions)
bloodhoundConfiguration = {
// We need to be notified on success, so we need a promise
initialize: false,
......@@ -41,94 +43,221 @@ var QfqNS = QfqNS || {};
wildcard: '%QUERY'
}
};
if ($element.val() !== '') {
// We prefetch the value provided
bloodhoundConfiguration.prefetch = {};
bloodhoundConfiguration.prefetch.url = n.TypeAhead.makePrefetchUrl(typeahead_endpoint, $element.val(), $element);
// Disable cache, we expect only a few entries. Caching gives sometimes strange behavior.
bloodhoundConfiguration.prefetch.cache = false;
// initialize typeahead (either with or without tags)
if ($element.data('typeahead-tags')) {
n.TypeAhead.installWithTags($element, bloodhoundConfiguration);
} else {
n.TypeAhead.installWithoutTags(typeahead_endpoint, $element, bloodhoundConfiguration);
}
$shadowElement = n.TypeAhead.makeShadowElement($element);
var suggestions = new Bloodhound(bloodhoundConfiguration);
var promise = suggestions.initialize();
promise.done((function ($element, suggestions) {
return function () {
n.TypeAhead.fillTypeAheadFromShadowElement($element, suggestions);
};
})($element, suggestions));
});
};
n.TypeAhead.installWithTags = function ($element, bloodhoundConfiguration) {
$element.typeahead({
hint: n.TypeAhead.getHint($element),
highlight: n.TypeAhead.getHighlight($element),
minLength: n.TypeAhead.getMinLength($element)
},
{
display: 'value',
source: suggestions,
limit: n.TypeAhead.getLimit($element),
templates: {
suggestion: function (obj) {
return "<div>" + n.TypeAhead.htmlEncode(obj.value) + "</div>";
},
// No message if field is not set to pedantic.
notFound: (function ($_) {
return function (obj) {
if (!!$_.data('typeahead-pedantic'))
return "<div>'" + n.TypeAhead.htmlEncode(obj.query) + "' not found";
};
})($element)
}
});
// initialize bloodhound (typeahead suggestion engine)
var suggestions = new Bloodhound(bloodhoundConfiguration);
suggestions.initialize();
// prevent form submit when enter key is pressed
$element.off('keyup');
$element.on('keypress keyup', function (e) {
var code = e.keyCode || e.which;
if (code === 13) {
e.preventDefault();
return false;
}
});
$element.bind('typeahead:select typeahead:autocomplete', function (event, suggestion) {
var $shadowElement = n.TypeAhead.getShadowElement($(event.delegateTarget));
$shadowElement.val(suggestion.key);
});
// list to keep tracks of existing tags and those added during the current session
// expected JSON format: [{value: "Alaska", key: "AK"}, {value: "Alabama", key: "AL"}]
var existingTags = $element.val() !== '' ? JSON.parse($element.val()) : [];
if (!!$element.data('typeahead-pedantic')) {
$element.bind('typeahead:change', n.TypeAhead.makePedanticHandler(suggestions));
$element.on('keydown', (function (suggestions) {
return function (event) {
if (event.which === 13) {
n.TypeAhead.makePedanticHandler(suggestions)(event);
}
};
})(suggestions));
// The pedantic handler will test if the shadow element has a value set (the KEY). If not, the
// typeahead element is cleared. Thus we have to guarantee that no value exists in the shadow
// element the instant the user starts typing since we don't know the outcome of the search.
//
// If we don't clear the shadow element the instant the user starts typing, and simply let the
// `typeahead:select` or `typeahead:autocomplete` handler set the selected value, the
// user might do following steps and end up in an inconsistent state:
//
// 1. Use typeahead to select/autocomplete a suggestion
// 2. delete the suggestion
// 3. enter a random string
// 4. submit form
//
// This would leave a stale value in the shadow element (from step 1.), and the pedantic handler
// would not clear the typeahead element, giving the impression the value in the typeahead element will be submitted.
$element.on('input', (function ($shadowElement) {
return function () {
$shadowElement.val('');
};
})($shadowElement));
// list of current typeahead suggestions
var typeaheadList = existingTags.slice();
// get list of possible keys a user can press to push a tag (list of keycodes)
var delimiters = $element.data('typeahead-tag-delimiters');
delimiters = delimiters !== undefined ? delimiters : [9, 13, 44];
// validator function for pedantic mode
var pedanticValidator = function (tag) {
// check if tag is in typeahead suggestions
var tagLookup = typeaheadList.filter(function (t) {return t.value.toLowerCase() === tag.toLowerCase();})[0];
return tagLookup !== undefined;
};
// initialize tagsManager
var tagApi = $element.tagsManager({
deleteTagsOnBackspace: false,
hiddenTagListName: $element.attr('name'),
tagClass: 'qfq-typeahead-tag',
delimiters: delimiters,
validator: !!$element.data('typeahead-pedantic') ? pedanticValidator : null,
});
// when tag is pushed, look up key and add it to existingTags
tagApi.bind('tm:pushed', function (e, tag) {
var tagLookup = typeaheadList.filter(function (t) {return t.value.toLowerCase() === tag.toLowerCase();})[0];
if (undefined === tagLookup) {
existingTags.push({key: 0, value: tag});
} else {
$element.bind('typeahead:change', function (event, suggestion) {
var $shadowElement = n.TypeAhead.getShadowElement($(event.delegateTarget));
// If none pendatic, suggestion key might not exist, so use suggestion instead.
$shadowElement.val(suggestion.key || suggestion);
});
existingTags.push({key: tagLookup.key, value: tagLookup.value});
}
});
// when the hidden input field changes, overwrite value with tag object list
tagApi.bind('tm:hiddenUpdate', function (e, tags, hiddenInput) {
var tagObjects = $.map(tags, function (t) {
return existingTags.filter(function (tt) {return tt.value === t;})[0];
});
hiddenInput.val(JSON.stringify(tagObjects));
});
$element.removeAttr('name');
// add existing tags
$.each(existingTags, function (i, tag) {
tagApi.tagsManager('pushTag', tag.value);
});
// add typahead
$element.typeahead({
// options
hint: n.TypeAhead.getHint($element),
highlight: n.TypeAhead.getHighlight($element),
minLength: n.TypeAhead.getMinLength($element)
}, {
display: 'value',
source: suggestions,
limit: n.TypeAhead.getLimit($element),
templates: {
suggestion: function (obj) {
return "<div>" + n.TypeAhead.htmlEncode(obj.value) + "</div>";
},
// No message if field is not set to pedantic.
notFound: (function ($_) {
return function (obj) {
if (!!$_.data('typeahead-pedantic'))
return "<div>'" + n.TypeAhead.htmlEncode(obj.query) + "' not found";
};
})($element)
}
});
// directly add tag when clicked on in typahead menu
$element.bind('typeahead:selected', function (event, sugg) {
tagApi.tagsManager("pushTag", sugg.value);
});
// update typahead list when typahead changes
$element.bind('typeahead:render', function (event, sugg) {
typeaheadList.length = 0;
typeaheadList.push.apply(typeaheadList, sugg);
});
};
n.TypeAhead.installWithoutTags = function (typeahead_endpoint, $element, bloodhoundConfiguration) {
var $shadowElement;
// Prefetch the value that is already in the field
if ($element.val() !== '') {
bloodhoundConfiguration.prefetch = {};
bloodhoundConfiguration.prefetch.url = n.TypeAhead.makePrefetchUrl(typeahead_endpoint, $element.val(), $element);
// Disable cache, we expect only a few entries. Caching gives sometimes strange behavior.
bloodhoundConfiguration.prefetch.cache = false;
}
// create a shadow element with the same value. This seems to be important for the pedantic mode. (?)
$shadowElement = n.TypeAhead.makeShadowElement($element);
// prefetch data
var suggestions = new Bloodhound(bloodhoundConfiguration);
var promise = suggestions.initialize();
// use shadow element to back fill field value, if it is in the fetched suggestions (why?)
promise.done((function ($element, suggestions) {
return function () {
n.TypeAhead.fillTypeAheadFromShadowElement($element, suggestions);
};
})($element, suggestions));
$element.typeahead({
// options
hint: n.TypeAhead.getHint($element),
highlight: n.TypeAhead.getHighlight($element),
minLength: n.TypeAhead.getMinLength($element)
},
{
// dataset
display: 'value',
source: suggestions,
limit: n.TypeAhead.getLimit($element),
templates: {
suggestion: function (obj) {
return "<div>" + n.TypeAhead.htmlEncode(obj.value) + "</div>";
},
// No message if field is not set to pedantic.
notFound: (function ($_) {
return function (obj) {
if (!!$_.data('typeahead-pedantic'))
return "<div>'" + n.TypeAhead.htmlEncode(obj.query) + "' not found";
};
})($element)
}
});
// bind select and autocomplete events
$element.bind('typeahead:select typeahead:autocomplete', function (event, suggestion) {
var $shadowElement = n.TypeAhead.getShadowElement($(event.delegateTarget));
$shadowElement.val(suggestion.key);
});
// bind change event
if (!!$element.data('typeahead-pedantic')) {
// Typeahead pedantic: Only allow suggested inputs
$element.bind('typeahead:change', n.TypeAhead.makePedanticHandler(suggestions));
$element.on('keydown', (function (suggestions) {
return function (event) {
if (event.which === 13) {
n.TypeAhead.makePedanticHandler(suggestions)(event);
}
};
})(suggestions));
// The pedantic handler will test if the shadow element has a value set (the KEY). If not, the
// typeahead element is cleared. Thus we have to guarantee that no value exists in the shadow
// element the instant the user starts typing since we don't know the outcome of the search.
//
// If we don't clear the shadow element the instant the user starts typing, and simply let the
// `typeahead:select` or `typeahead:autocomplete` handler set the selected value, the
// user might do following steps and end up in an inconsistent state:
//
// 1. Use typeahead to select/autocomplete a suggestion
// 2. delete the suggestion
// 3. enter a random string
// 4. submit form
//
// This would leave a stale value in the shadow element (from step 1.), and the pedantic handler
// would not clear the typeahead element, giving the impression the value in the typeahead element will be submitted.
$element.on('input', (function ($shadowElement) {
return function () {
$shadowElement.val('');
};
})($shadowElement));
} else {
$element.bind('typeahead:change', function (event, suggestion) {
var $shadowElement = n.TypeAhead.getShadowElement($(event.delegateTarget));
// If none pendatic, suggestion key might not exist, so use suggestion instead.
$shadowElement.val(suggestion.key || suggestion);
});
}
};
n.TypeAhead.makePedanticHandler = function (bloodhound) {
return function (event) {
var $typeAhead = $(event.delegateTarget);
......
......@@ -665,6 +665,11 @@ select.qfq-locked:invalid {
width: 100%;
}
// typeAhead tags
span.qfq-typeahead-tag {
margin-right: 5px;
}
@media (min-width: 768px) {
.form-horizontal .control-label {
text-align: unset;
......
......@@ -124,6 +124,56 @@
</div>
<?php
$tags = [
['value' => "Alabama", 'key' => "AL"],
['value' => "Alaska", 'key' => "AK"]
];
$tagsSafeJson = htmlentities(json_encode($tags), ENT_QUOTES, 'UTF-8');
?>
<div id="formgroup4" class="form-group">
<div class="col-md-2">
<label for="tags1" class="control-label">Text input 4 (tags)</label>
</div>
<div class="col-md-6">
<input id="tags1" type="text" class="form-control qfq-typeahead" name="tags1"
data-typeahead-sip="abcdef"
data-typeahead-limit="10"
data-typeahead-minlength="1"
data-typeahead-tags="true"
data-typeahead-tag-delimiters="[9, 13]"
value="<?php echo $tagsSafeJson; ?>"
>
</div>
</div>
<div id="formgroup5" class="form-group">
<div class="col-md-2">
<label for="tags2" class="control-label">Text input 4 (tags, pedantic)</label>
</div>
<div class="col-md-6">
<input id="tags2" type="text" class="form-control qfq-typeahead" name="tags2"
data-typeahead-sip="abcdef"
data-typeahead-limit="10"
data-typeahead-minlength="1"
data-typeahead-tags="true"
data-typeahead-pedantic="true"
data-typeahead-tag-delimiters="[9, 44]"
value="<?php echo $tagsSafeJson; ?>"
>
</div>
</div>
</form>
</div>
......
Supports Markdown
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