-
-
Notifications
You must be signed in to change notification settings - Fork 328
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
Meteor AutoForm 7.1.0 - Autocomplete #1701
Open
4Z4T4R
wants to merge
16
commits into
Meteor-Community-Packages:devel
Choose a base branch
from
4Z4T4R:azatar/devel
base: devel
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 11 commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
fab336c
Introducing Autocomplete for Meteor AutoForm
4Z4T4R a06e00b
Meteor AutoForm 7.1.0 - Native Autocomplete
4Z4T4R b9da3c7
Meteor AutoForm v7.1.0 - Autocomplete
4Z4T4R 3538e85
Meteor AutoForm v7.1.0 - Autocomplete
4Z4T4R a3019f4
Meteor AutoForm v7.1.0 - Autocomplete
4Z4T4R 2f020e6
Meteor AutoForm v7.1.0 - Autocomplete
4Z4T4R b18f721
Meteor AutoForm v7.1.0 - Autocomplete
4Z4T4R 5930cc3
Meteor AutoForm v7.1.0 - Autocomplete
4Z4T4R b5ba2e4
inputTypes autocomplete update options reactively
jankapunkt fed965d
inputTypes add missing ReactiveVar import
jankapunkt a16bf66
inputTypes autocomplete added explicit touch support
jankapunkt 2558865
autocomplete ui and reactivity fixed
jankapunkt 68d5d4c
inputTypes autoComplete added comment on updateValue function
jankapunkt 3077ce1
downgraded version to 7.0.0 until final release to avoid ci conflicts
jankapunkt 6a0df5e
inputTypes autocomplete corrected event bubbling on click and tab
jankapunkt 738c91e
inputTypes autocomplete emit change event only on empty (falsy) or va…
jankapunkt File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
<template name="afAutocomplete"> | ||
<div class="autocomplete dropdown"> | ||
<input type="text" value="{{this.label}}" {{this.visibleAtts}} /> | ||
<div class="dropdown-menu"></div> | ||
</div> | ||
<input type="hidden" value="{{this.value}}" {{this.atts}} /> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,238 @@ | ||
import { Template } from 'meteor/templating' | ||
import { ReactiveVar } from 'meteor/reactive-var' | ||
|
||
AutoForm.addInputType('autocomplete', { | ||
template: 'afAutocomplete', | ||
valueOut: function () { | ||
return this.val() | ||
}, | ||
valueConverters: { | ||
stringArray: AutoForm.valueConverters.stringToStringArray, | ||
number: AutoForm.valueConverters.stringToNumber, | ||
numberArray: AutoForm.valueConverters.stringToNumberArray, | ||
boolean: AutoForm.valueConverters.stringToBoolean, | ||
booleanArray: AutoForm.valueConverters.stringToBooleanArray, | ||
date: AutoForm.valueConverters.stringToDate, | ||
dateArray: AutoForm.valueConverters.stringToDateArray | ||
}, | ||
contextAdjust: function (context) { | ||
context.atts.autocomplete = 'off' | ||
const itemAtts = { ...context.atts } | ||
// remove non-essential atts from visible input | ||
const visibleAtts = Object.assign({}, { ...context.atts }) | ||
const keys = ['data-schema-key', 'id', 'name'] | ||
keys.forEach(key => { | ||
delete visibleAtts[key] | ||
}) | ||
// add form-control to remaining classes | ||
context.visibleAtts = AutoForm.Utility.addClass({ ...visibleAtts }, 'form-control') | ||
context.atts = AutoForm.Utility.addClass({ ...itemAtts }, 'form-control') | ||
// build items list | ||
context.items = [] | ||
|
||
// re-use selectOptions to keep it DRY | ||
// Add all defined options or default | ||
if (context.selectOptions) { | ||
context.selectOptions.forEach(function (opt) { | ||
// there are no subgroups here | ||
const { label, value, ...htmlAtts } = opt | ||
context.items.push({ | ||
name: context.name, | ||
label, | ||
value, | ||
htmlAtts, | ||
_id: opt.value.toString(), | ||
selected: (opt.value === context.value), | ||
atts: itemAtts | ||
}) | ||
}) | ||
} | ||
else { | ||
console.warn('autocomplete requires options for suggestions.') | ||
} | ||
return context | ||
} | ||
}) | ||
|
||
Template.afAutocomplete.onRendered(function () { | ||
/* AUTOCOMPLETE | ||
*************** | ||
* This uses the same datums as select types, which | ||
* means that 'options' come from simple-schema. | ||
* | ||
* It allows selection by arrows up/down/enter; mouse click; | ||
* and when enough characters entered make a positive match. | ||
* Arrow navigation is circlular; top to bottom & vice versa. | ||
* | ||
* It uses the 'dropdown' classes in bootstrap 4 for styling. | ||
*/ | ||
|
||
// get the instance items | ||
// defined in several ways | ||
const me = Template.instance() | ||
const items = new ReactiveVar([]) | ||
me.autorun(() => { | ||
const data = Template.currentData() | ||
items.set(data.items) | ||
}) | ||
|
||
// secure the dom so multiple autocompletes don't clash | ||
const $input = me.$('input[type="text"]') | ||
const $hidden = me.$('input[type="hidden"]') | ||
const $container = me.$('.dropdown') | ||
const $suggestions = me.$('.dropdown-menu') | ||
|
||
// prepare for arrow navigation | ||
let currIndex = -1 | ||
let totalItems = 0 | ||
let showing = false | ||
|
||
const clearDropdown = function (e, haltEvents = false) { | ||
if (showing === true) { | ||
// hide the menu and reset the params | ||
$suggestions.empty().removeClass('show') | ||
$container.removeClass('show') | ||
currIndex = -1 | ||
totalItems = 0 | ||
showing = false | ||
if (haltEvents === true) { | ||
e.preventDefault() | ||
e.stopPropagation() | ||
} | ||
} | ||
} | ||
|
||
// keydown catches escape | ||
$input.keydown((e) => { | ||
// prevent form submit from "Enter/Return" if showing | ||
if ( | ||
/Enter/.test(e.originalEvent.key) === true && | ||
showing === true | ||
) { | ||
e.preventDefault() | ||
e.stopPropagation() | ||
} | ||
// allow Escape to close the dropdown | ||
else if ( | ||
/Escape/.test(e.originalEvent.key) === true && | ||
showing === true | ||
) { | ||
clearDropdown(e, true) | ||
} | ||
}) | ||
|
||
// clear on blur? | ||
// TODO: Figure out how blur won't block "click" | ||
// $input.blur((e)=>{ clearDropdown(e) }) | ||
|
||
const callback = function (e) { | ||
// only populate when typing characters or deleting | ||
// otherwise, we are navigating | ||
if (/ArrowDown|ArrowUp|ArrowLeft|ArrowRight|Enter|Escape/.test(e.originalEvent.key) === false) { | ||
// we're typing | ||
// ensure hidden and visible values match for validation | ||
$hidden.val($input.val()) | ||
// filter results from visible input value | ||
const result = items.get().filter((i) => { | ||
const reg = new RegExp(e.target.value, 'gi') | ||
return reg.test(i.label) | ||
}) | ||
|
||
// display results in 'suggestions' div | ||
$suggestions.empty() | ||
let html | ||
const len = result.length | ||
totalItems = result.length | ||
|
||
if (len > 1) { | ||
currIndex = -1 | ||
for (let i = 0; i < len; i++) { | ||
// populate suggestions | ||
html = `<div class="dropdown-item" data-value="${result[i].value}" data-label="${result[i].label}">${result[i].label}</div>` | ||
$suggestions.append(html) | ||
} | ||
$suggestions.addClass('show') | ||
$container.addClass('show') | ||
showing = true | ||
|
||
// clear any manual navigated selections on hover | ||
$suggestions.children().hover((e) => { | ||
$suggestions.children().removeClass('active') | ||
currIndex = -1 | ||
}) | ||
|
||
// choose an answer on click | ||
$suggestions.children().click((e) => { | ||
const dataValue = me.$(e.target).attr('data-value') | ||
const dataLabel = me.$(e.target).attr('data-label') | ||
$input.val(dataLabel) | ||
$hidden.val(dataValue) | ||
clearDropdown(e, false) | ||
$input.focus() | ||
}) | ||
} | ||
else if (e.originalEvent.key !== 'Backspace') { | ||
// only force populate if not deleting | ||
// bc we all make mistakes | ||
if (result.length === 1) { | ||
$input.val(result[0].label) | ||
$hidden.val(result[0].value) | ||
clearDropdown(e, false) | ||
$input.focus() | ||
} | ||
else { | ||
// no results, hide | ||
clearDropdown(e, false) | ||
} | ||
} | ||
} | ||
else if (showing === true) { // we're navigating suggestions | ||
// start highlighting at the 0 index | ||
if (/ArrowDown/.test(e.originalEvent.key) === true) { | ||
// navigating down | ||
if (currIndex === totalItems - 1) { | ||
currIndex = -1 | ||
} | ||
// remove all classes from the children | ||
$suggestions.children().removeClass('active') | ||
$suggestions.children('div').eq(++currIndex).addClass('active') | ||
} | ||
else if (/ArrowUp/.test(e.originalEvent.key) === true) { | ||
if (currIndex <= 0) { | ||
currIndex = totalItems | ||
} | ||
// navigating up | ||
// remove all classes from the children | ||
$suggestions.children().removeClass('active') | ||
$suggestions.children('div').eq(--currIndex).addClass('active') | ||
} | ||
else if (/Enter/.test(e.originalEvent.key) === true) { | ||
// we're selecting | ||
if (currIndex === -1) { | ||
currIndex = 0 | ||
} | ||
const enterValue = $suggestions.children('div').eq(currIndex).attr('data-value') | ||
const enterLabel = $suggestions.children('div').eq(currIndex).attr('data-label') | ||
$input.val(enterLabel) | ||
$hidden.val(enterValue) | ||
clearDropdown(e, false) | ||
$input.focus() | ||
} | ||
} | ||
} | ||
|
||
// detect keystrokes | ||
$input.keyup((e) => { | ||
callback(e) | ||
}) | ||
|
||
// show on double click | ||
$input.dblclick((e) => { | ||
callback(e) | ||
}) | ||
|
||
// show on double click | ||
$input.on('touchstart', (e) => { | ||
callback(e) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
change comment to
show on touch
Good catch!