Skip to content

Commit

Permalink
[chore] refactored dropdown methods so they wouldn't need binding wit…
Browse files Browse the repository at this point in the history
…h "bind" or "call"
  • Loading branch information
Yair Even Or authored and Yair Even Or committed Jun 12, 2021
1 parent 95e25b6 commit d780696
Show file tree
Hide file tree
Showing 9 changed files with 94 additions and 77 deletions.
23 changes: 14 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
- [Drag & Sort](#drag--sort)
- [Integration example:](#integration-example)
- [DOM Templates](#dom-templates)
- [Suggestions selectbox](#suggestions-selectbox)
- [Suggestions list](#suggestions-list)
- [Example for a suggestion item alias](#example-for-a-suggestion-item-alias)
- [Example whitelist:](#example-whitelist)
- [Mixed-Content](#mixed-content)
Expand Down Expand Up @@ -92,7 +92,7 @@ var tagify = new Tagify(...)
* Supports [single-value](#single-value) mode (like `<select>`)
* Supports whitelist/blacklist
* Supports Templates for: <em>component wrapper</em>, <em>tag items</em>, <em>suggestion list</em> & <em>suggestion items</em>
* Shows suggestions selectbox (flexiable settings & styling) at *full (component) width* or *next to* the typed texted (caret)
* Shows suggestions list (flexiable settings & styling) at *full (component) width* or *next to* the typed texted (caret)
* Allows setting suggestions' [aliases](#example-for-a-suggestion-item-alias) for easier fuzzy-searching
* Auto-suggest input as-you-type with ability to auto-complete
* Can paste in multiple values: `tag 1, tag 2, tag 3` or even newline-separated tags
Expand Down Expand Up @@ -203,13 +203,13 @@ function onInput( e ){
controller = new AbortController()

// show loading animation and hide the suggestions dropdown
tagify.loading(true).dropdown.hide.call(tagify)
tagify.loading(true).dropdown.hide()

fetch('http://get_suggestions.com?value=' + value, {signal:controller.signal})
.then(RES => RES.json())
.then(function(newWhitelist){
tagify.whitelist = newWhitelist // update inwhitelist Array in-place
tagify.loading(false).dropdown.show.call(tagify, value) // render the suggestions dropdown
tagify.loading(false).dropdown.show(value) // render the suggestions dropdown
})
}
```
Expand Down Expand Up @@ -267,16 +267,21 @@ which is a special template for rendering a suggestion item (in the dropdown lis

[View templates](https://github.com/yairEO/tagify/blob/master/src/parts/templates.js)

## Suggestions selectbox
The suggestions selectbox is a *whitelist Array* of *Strings* or *Objects* which was set in the [settings](#settings) Object when the Tagify instance was created, and can be set latet directly on the instance: `tagifyInstance.whitelist = ["tag1", "tag2", ...]`.
## Suggestions list

<p align="center">
<img src="/docs/suggestions-list.apng" alt='suggestions list dropdown'/>
</p>

The suggestions list is a *whitelist Array* of *Strings* or *Objects* which was set in the [settings](#settings) Object when the Tagify instance was created, and can be set latet directly on the instance: `tagifyInstance.whitelist = ["tag1", "tag2", ...]`.

The suggestions dropdown will be appended to the document's `<body>` element and will be rendered by default in a position below (bottom of) the Tagify element.
Using the keyboard arrows up/down will highlight an option from the list, and hitting the Enter key to select.

It is possible to tweak the selectbox dropdown via 2 settings:
It is possible to tweak the list dropdown via 2 settings:

- `enabled` - this is a numeral value which tells Tagify when to show the suggestions dropdown, when a minimum of N characters were typed.
- `maxItems` - Limits the number of items the suggestions selectbox will render
- `maxItems` - Limits the number of items the suggestions list will render

```javascript
var input = document.querySelector('input'),
Expand Down Expand Up @@ -890,7 +895,7 @@ a11y.*focusableTags* | <sub>Boolean</sub> | false
dropdown.*enabled* | <sub>Number</sub> | 2 | Minimum characters input for showing a suggestions list. `false` will not render a suggestions list.
dropdown.*caseSensitive* | <sub>Boolean</sub> | false | if `true`, match **exact** item when a suggestion is selected (from the dropdown) and also more strict matching for dulpicate items. **Ensure** `fuzzySearch` is `false` for this to work.
dropdown.*maxItems* | <sub>Number</sub> | 10 | Maximum items to show in the suggestions list
dropdown.*classname* | <sub>String</sub> | `""` | Custom *classname* for the dropdown suggestions selectbox
dropdown.*classname* | <sub>String</sub> | `""` | Custom *classname* for the dropdown suggestions list
dropdown.*fuzzySearch* | <sub>Boolean</sub> | true | Enables filtering dropdown items values' by string *containing* and not only *beginning*
dropdown.*accentedSearch* | <sub>Boolean</sub> | true | Enable searching for <em>accented</em> items in the whitelist without typing exact match (#491)
dropdown.*position* | <sub>String</sub> | `"all"` | <ul><li>`manual` - will not render the dropdown, and you would need to do it yourself. [See demo](https://yaireo.github.io/tagify/#section-manual-suggestions)</li><li>`text` - places the dropdown next to the caret</li><li>`input` - places the dropdown next to the input (useful in rare situations)</li><li>`all` - normal, full-width design</li></ul>
Expand Down
2 changes: 1 addition & 1 deletion dist/jQuery.tagify.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/tagify.min.js

Large diffs are not rendered by default.

Binary file added docs/suggestions-list.apng
Binary file not shown.
14 changes: 7 additions & 7 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1300,11 +1300,11 @@ <h3>JAVASCRIPT</h3>
tagify.settings.whitelist = result.concat(tagify.value) // add already-existing tags to the new whitelist array

tagify
.loading(false)
// render the suggestions dropdown.
.dropdown.show.call(tagify, e.detail.value);
.loading(false)
// render the suggestions dropdown.
.dropdown.show(e.detail.value);
})
.catch(err => tagify.dropdown.hide.call(tagify))
.catch(err => tagify.dropdown.hide())
}

function onTagEdit(e){
Expand Down Expand Up @@ -1388,7 +1388,7 @@ <h3>JAVASCRIPT</h3>

// https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentElement
function renderSuggestionsList(){
tagify.dropdown.show.call(tagify) // load the list
tagify.dropdown.show() // load the list
tagify.DOM.scope.parentNode.appendChild(tagify.DOM.dropdown)
}
})()
Expand Down Expand Up @@ -1510,7 +1510,7 @@ <h3>JAVASCRIPT</h3>
tagify.whitelist = whitelist_2;

if( e.detail.value.length > 1 )
tagify.dropdown.show.call(tagify, e.detail.value);
tagify.dropdown.show(e.detail.value);
}

console.log( tagify.value )
Expand Down Expand Up @@ -1755,7 +1755,7 @@ <h3>JAVASCRIPT</h3>

function onSelectSuggestion(e){
if( e.detail.elm == addAllSuggestionsElm )
tagify.dropdown.selectAll.call(tagify);
tagify.dropdown.selectAll();
}

// create a "add all" custom suggestion element every time the dropdown changes
Expand Down
72 changes: 42 additions & 30 deletions src/parts/dropdown.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
import { sameStr, isObject, minify, escapeHTML, extend, unaccent, getNodeHeight } from './helpers'

export function initDropdown(){
this.dropdown = {}

for( let p in this._dropdown )
this.dropdown[p] = typeof this._dropdown[p] === 'function'
? this._dropdown[p].bind(this)
: this._dropdown[p]

if( this.settings.dropdown.enabled >= 0 )
this.dropdown.init()
}

export default {
init(){
this.DOM.dropdown = this.parseTemplate('dropdown', [this.settings])
Expand Down Expand Up @@ -33,7 +45,7 @@ export default {
// if no value was supplied, show all the "whitelist" items in the dropdown
// @type [Array] listItems
// TODO: add a Setting to control items' sort order for "listItems"
this.suggestedListItems = this.dropdown.filterListItems.call(this, value)
this.suggestedListItems = this.dropdown.filterListItems(value)

// trigger at this exact point to let the developer the chance to manually set "this.suggestedListItems"
if( value && !this.suggestedListItems.length ){
Expand All @@ -59,7 +71,7 @@ export default {
// hide suggestions list if no suggestion matched
else{
this.input.autocomplete.suggest.call(this);
this.dropdown.hide.call(this)
this.dropdown.hide()
return;
}
}
Expand All @@ -74,10 +86,10 @@ export default {
}
}

this.dropdown.fill.call(this, noMatchListItem)
this.dropdown.fill(noMatchListItem)

if( _s.dropdown.highlightFirst )
this.dropdown.highlightOption.call(this, this.DOM.dropdown.content.children[0])
this.dropdown.highlightOption(this.DOM.dropdown.content.children[0])

// bind events, exactly at this stage of the code. "dropdown.show" method is allowed to be
// called multiple times, regardless if the dropdown is currently visible, but the events-binding
Expand All @@ -99,8 +111,8 @@ export default {
// a slight delay is needed if the dropdown "position" setting is "text", and nothing was typed in the input,
// so sadly the "getCaretGlobalPosition" method doesn't recognize the caret position without this delay
setTimeout(() => {
this.dropdown.position.call(this)
this.dropdown.render.call(this)
this.dropdown.position()
this.dropdown.render()
})
}

Expand Down Expand Up @@ -165,7 +177,7 @@ export default {
// append the dropdown to the body element & handle events
if( !document.body.contains(this.DOM.dropdown) ){
this.DOM.dropdown.classList.add( _s.classNames.dropdownInital )
this.dropdown.position.call(this, ddHeight)
this.dropdown.position(ddHeight)
_s.dropdown.appendTarget.appendChild(this.DOM.dropdown)

setTimeout(() =>
Expand All @@ -183,7 +195,7 @@ export default {
fill( HTMLContent ){
HTMLContent = typeof HTMLContent == 'string'
? HTMLContent
: this.dropdown.createListHTML.call(this, HTMLContent || this.suggestedListItems)
: this.dropdown.createListHTML(HTMLContent || this.suggestedListItems)

this.DOM.dropdown.content.innerHTML = minify(HTMLContent)
},
Expand All @@ -194,12 +206,12 @@ export default {
*/
refilter( value ){
value = value || this.state.dropdown.query || ''
this.suggestedListItems = this.dropdown.filterListItems.call(this, value)
this.suggestedListItems = this.dropdown.filterListItems(value)

this.dropdown.fill.call(this)
this.dropdown.fill()

if( !this.suggestedListItems.length )
this.dropdown.hide.call(this)
this.dropdown.hide()

this.trigger("dropdown:updated", this.DOM.dropdown)
},
Expand Down Expand Up @@ -303,7 +315,7 @@ export default {
onKeyDown(e){
// get the "active" element, and if there was none (yet) active, use first child
var selectedElm = this.DOM.dropdown.querySelector(this.settings.classNames.dropdownItemActiveSelector),
selectedElmData = this.dropdown.getSuggestionDataByNode.call(this, selectedElm)
selectedElmData = this.dropdown.getSuggestionDataByNode(selectedElm)

switch( e.key ){
case 'ArrowDown' :
Expand All @@ -322,14 +334,14 @@ export default {
selectedElm = dropdownItems[e.key == 'ArrowUp' || e.key == 'Up' ? dropdownItems.length - 1 : 0];
}

selectedElmData = this.dropdown.getSuggestionDataByNode.call(this, selectedElm)
selectedElmData = this.dropdown.getSuggestionDataByNode(selectedElm)

this.dropdown.highlightOption.call(this, selectedElm, true);
this.dropdown.highlightOption(selectedElm, true);
break;
}
case 'Escape' :
case 'Esc': // IE11
this.dropdown.hide.call(this);
this.dropdown.hide();
break;

case 'ArrowRight' :
Expand All @@ -339,7 +351,7 @@ export default {
// in mix-mode, treat arrowRight like Enter key, so a tag will be created
if( this.settings.mode != 'mix' && selectedElm && !this.settings.autoComplete.rightKey && !this.state.editing ){
e.preventDefault() // prevents blur so the autocomplete suggestion will not become a tag
var value = this.dropdown.getMappedValue.call(this, selectedElmData)
var value = this.dropdown.getMappedValue(selectedElmData)

this.input.autocomplete.set.call(this, value)
return false
Expand All @@ -352,9 +364,9 @@ export default {
this.settings.hooks.suggestionClick(e, {tagify:this, tagData:selectedElmData, suggestionElm:selectedElm})
.then(() => {
if( selectedElm )
this.dropdown.selectOption.call(this, selectedElm)
this.dropdown.selectOption(selectedElm)
else
this.dropdown.hide.call(this)
this.dropdown.hide()

if( this.settings.mode != 'mix' )
this.addTags(this.state.inputText.trim(), true)
Expand All @@ -381,19 +393,19 @@ export default {
onMouseOver(e){
var ddItem = e.target.closest(this.settings.classNames.dropdownItemSelector)
// event delegation check
ddItem && this.dropdown.highlightOption.call(this, ddItem)
ddItem && this.dropdown.highlightOption(ddItem)
},

onMouseLeave(e){
// de-highlight any previously highlighted option
this.dropdown.highlightOption.call(this)
this.dropdown.highlightOption()
},

onClick(e){
if( e.button != 0 || e.target == this.DOM.dropdown || e.target == this.DOM.dropdown.content ) return; // allow only mouse left-clicks

var selectedElm = e.target.closest(this.settings.classNames.dropdownItemSelector),
selectedElmData = this.dropdown.getSuggestionDataByNode.call(this, selectedElm)
selectedElmData = this.dropdown.getSuggestionDataByNode(selectedElm)

// temporary set the "actions" state to indicate to the main "blur" event it shouldn't run
this.state.actions.selectOption = true;
Expand All @@ -402,9 +414,9 @@ export default {
this.settings.hooks.suggestionClick(e, {tagify:this, tagData:selectedElmData, suggestionElm:selectedElm})
.then(() => {
if( selectedElm )
this.dropdown.selectOption.call(this, selectedElm)
this.dropdown.selectOption(selectedElm)
else
this.dropdown.hide.call(this)
this.dropdown.hide()
})
.catch(err => err)
},
Expand Down Expand Up @@ -462,7 +474,7 @@ export default {
// Try to autocomplete the typed value with the currently highlighted dropdown item
if( this.settings.autoComplete ){
this.input.autocomplete.suggest.call(this, itemData)
this.dropdown.position.call(this) // suggestions might alter the height of the tagify wrapper because of unkown suggested term length that could drop to the next line
this.dropdown.position() // suggestions might alter the height of the tagify wrapper because of unkown suggested term length that could drop to the next line
}
},

Expand All @@ -475,7 +487,7 @@ export default {

if( !elm ) {
this.addTags(this.state.inputText, true)
closeOnSelect && this.dropdown.hide.call(this)
closeOnSelect && this.dropdown.hide()
return;
}

Expand All @@ -489,7 +501,7 @@ export default {

// above event must be triggered, regardless of anything else which might go wrong
if( !tagifySuggestionIdx || !tagData ){
this.dropdown.hide.call(this)
this.dropdown.hide()
return
}

Expand All @@ -512,18 +524,18 @@ export default {
}

else
this.dropdown.refilter.call(this)
this.dropdown.refilter()
},

selectAll(){
// having suggestedListItems with items messes with "normalizeTags" when wanting
// to add all tags
this.suggestedListItems.length = 0;
this.dropdown.hide.call(this)
this.dropdown.hide()

// some whitelist items might have already been added as tags so when addings all of them,
// skip adding already-added ones, so best to use "filterListItems" method over "settings.whitelist"
this.addTags(this.dropdown.filterListItems.call(this, ''), true)
this.addTags(this.dropdown.filterListItems(''), true)
return this
},

Expand Down Expand Up @@ -637,7 +649,7 @@ export default {
if( typeof suggestion == 'string' || typeof suggestion == 'number' )
suggestion = {value:suggestion}

var value = this.dropdown.getMappedValue.call(this, suggestion)
var value = this.dropdown.getMappedValue(suggestion)

suggestion.value = value && typeof value == 'string'
? escapeHTML(value)
Expand Down
Loading

0 comments on commit d780696

Please sign in to comment.