From 1f03bc4227bc1ec740fc6990bd42dd02b3d06ce8 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Fri, 18 Jun 2021 23:30:43 +0200 Subject: [PATCH 1/2] Use data attributes instead of class names to identify html elements --- README.md | 2 +- index.html | 4 ++-- src/parts/defaults.js | 9 ++++++++- src/parts/dropdown.js | 6 +++--- src/parts/events.js | 18 +++++++++--------- src/parts/templates.js | 11 +++++++---- src/tagify.js | 32 +++++++++++++++++--------------- 7 files changed, 47 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index d7074758..981580ac 100644 --- a/README.md +++ b/README.md @@ -245,7 +245,7 @@ var tagify = new Tagify(inputElement) // bind "DragSort" to Tagify's main element and tell // it that all the items with the below "selector" are "draggable" var dragsort = new DragSort(tagify.DOM.scope, { - selector: '.'+tagify.settings.classNames.tag, + selector: tagify.settings.tagSelector, callbacks: { dragEnd: onDragEnd } diff --git a/index.html b/index.html index 5ac4b4f5..be76f85c 100644 --- a/index.html +++ b/index.html @@ -1566,7 +1566,7 @@

JAVASCRIPT

}, templates: { dropdownItemNoMatch: function(data) { - return `
+ return `
No suggestion found for: ${data.value}
` } @@ -2108,7 +2108,7 @@

JAVASCRIPT

// using 3-party script "dragsort" // https://github.com/yairEO/dragsort var dragsort = new DragSort(tagify.DOM.scope, { - selector:'.'+tagify.settings.classNames.tag, + selector: tagify.settings.tagSelector, callbacks: { dragEnd: onDragEnd } diff --git a/src/parts/defaults.js b/src/parts/defaults.js index b5554757..04583360 100644 --- a/src/parts/defaults.js +++ b/src/parts/defaults.js @@ -62,6 +62,13 @@ export default { empty : 'tagify--empty', }, + inputSelector: "[data-tagify-control='input']", + tagSelector: "[data-tagify-control='tag']", + tagTextSelector: "[data-tagify-control='tagText']", + tagRemoveBtn: "[data-tagify-control='tagRemoveBtn']", + dropdownWrapperSelector: "[data-tagify-control='dropdownWrapper']", + dropdownItemSelector: "[data-tagify-control='dropdownItem']", + dropdown: { classname : '', enabled : 2, // minimum input characters to be typed for the suggestions dropdown to show @@ -82,4 +89,4 @@ export default { beforePaste: () => Promise.resolve(), suggestionClick: () => Promise.resolve() } -} \ No newline at end of file +} diff --git a/src/parts/dropdown.js b/src/parts/dropdown.js index 293e39f4..156b3cac 100644 --- a/src/parts/dropdown.js +++ b/src/parts/dropdown.js @@ -15,7 +15,7 @@ export function initDropdown(){ export default { init(){ this.DOM.dropdown = this.parseTemplate('dropdown', [this.settings]) - this.DOM.dropdown.content = this.DOM.dropdown.querySelector(this.settings.classNames.dropdownWrapperSelector) + this.DOM.dropdown.content = this.DOM.dropdown.querySelector(this.settings.dropdownWrapperSelector) }, /** @@ -391,7 +391,7 @@ export default { }, onMouseOver(e){ - var ddItem = e.target.closest(this.settings.classNames.dropdownItemSelector) + var ddItem = e.target.closest(this.settings.dropdownItemSelector) // event delegation check ddItem && this.dropdown.highlightOption(ddItem) }, @@ -404,7 +404,7 @@ export default { 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), + var selectedElm = e.target.closest(this.settings.dropdownItemSelector), selectedElmData = this.dropdown.getSuggestionDataByNode(selectedElm) // temporary set the "actions" state to indicate to the main "blur" event it shouldn't run diff --git a/src/parts/events.js b/src/parts/events.js index 30efc2a0..bac185e7 100644 --- a/src/parts/events.js +++ b/src/parts/events.js @@ -89,7 +89,7 @@ export default { eventData = {relatedTarget:e.relatedTarget}, isTargetSelectOption = this.state.actions.selectOption && (ddEnabled || !_s.dropdown.closeOnSelect), isTargetAddNewBtn = this.state.actions.addNew && ddEnabled, - isRelatedTargetX = e.relatedTarget && e.relatedTarget.classList.contains(_s.classNames.tag) && this.DOM.scope.contains(e.relatedTarget), + isRelatedTargetX = e.relatedTarget && e.relatedTarget.dataset.tagifyControl === 'tag' && this.DOM.scope.contains(e.relatedTarget), shouldAddTags; if( type == 'blur' ){ @@ -161,7 +161,7 @@ export default { onWindowKeyDown(e){ var focusedElm = document.activeElement, - isTag = focusedElm.classList.contains(this.settings.classNames.tag), + isTag = focusedElm.dataset.tagifyControl === 'tag', isBelong = isTag && this.DOM.scope.contains(document.activeElement), nextTag; @@ -563,7 +563,7 @@ export default { onClickScope(e){ var _s = this.settings, - tagElm = e.target.closest('.' + _s.classNames.tag), + tagElm = e.target.closest(_s.tagSelector), timeDiffFocus = +new Date() - this.state.hasFocus; if( e.target == this.DOM.scope ){ @@ -572,7 +572,7 @@ export default { return } - else if( e.target.classList.contains(_s.classNames.tagX) ){ + else if( e.target.dataset.tagifyControl === 'tagRemoveBtn'){ this.removeTags( e.target.parentNode ) return } @@ -642,7 +642,7 @@ export default { }, onEditTagInput( editableElm, e ){ - var tagElm = editableElm.closest('.' + this.settings.classNames.tag), + var tagElm = editableElm.closest(this.settings.tagSelector), tagElmIdx = this.getNodeIndex(tagElm), tagData = this.tagData(tagElm), value = this.input.normalize.call(this, editableElm), @@ -694,7 +694,7 @@ export default { if( !this.DOM.scope.contains(editableElm) ) return; var _s = this.settings, - tagElm = editableElm.closest('.' + _s.classNames.tag), + tagElm = editableElm.closest(_s.tagSelector), textValue = this.input.normalize.call(this, editableElm), originalData = this.tagData(tagElm).__originalData, // pre-edit data hasChanged = tagElm.innerHTML != tagElm.__tagifyTagData.__originalHTML, @@ -768,14 +768,14 @@ export default { }, onDoubleClickScope(e){ - var tagElm = e.target.closest('.' + this.settings.classNames.tag), + var tagElm = e.target.closest(this.settings.tagSelector), _s = this.settings, isEditingTag, isReadyOnlyTag; if( !tagElm ) return - isEditingTag = tagElm.classList.contains(this.settings.classNames.tagEditing) + isEditingTag = tagElm.dataset.tagifyTagStatus === 'editing' isReadyOnlyTag = tagElm.hasAttribute('readonly') if( _s.mode != 'select' && !_s.readonly && !isEditingTag && !isReadyOnlyTag && this.settings.editTags ) @@ -785,4 +785,4 @@ export default { this.trigger('dblclick', { tag:tagElm, index:this.getNodeIndex(tagElm), data:this.tagData(tagElm) }) } } -} \ No newline at end of file +} diff --git a/src/parts/templates.js b/src/parts/templates.js index eb3297e0..5310273d 100644 --- a/src/parts/templates.js +++ b/src/parts/templates.js @@ -11,6 +11,7 @@ export default { tabIndex="-1"> @@ -23,10 +24,11 @@ export default { spellcheck='false' tabIndex="${this.settings.a11y.focusableTags ? 0 : -1}" class="${this.settings.classNames.tag} ${tagData.class ? tagData.class : ""}" + data-tagify-control='tag' ${this.getAttributes(tagData)}> - +
- ${tagData[this.settings.tagTextProp] || tagData.value} + ${tagData[this.settings.tagTextProp] || tagData.value}
` }, @@ -37,16 +39,17 @@ export default { className = `${settings.classNames.dropdown}`; return `
-
+
` }, dropdownItem( item ){ return `
${item.value}
` }, dropdownItemNoMatch: null -} \ No newline at end of file +} diff --git a/src/tagify.js b/src/tagify.js index deb3116b..56a8d868 100644 --- a/src/tagify.js +++ b/src/tagify.js @@ -101,11 +101,6 @@ Tagify.prototype = { _s.placeholder = input.getAttribute('placeholder') || _s.placeholder || "" _s.required = input.hasAttribute('required') - for( let name in _s.classNames ) - Object.defineProperty(_s.classNames, name + "Selector" , { - get(){ return "."+this[name].split(" ").join(".") } - }) - if( this.isIE ) _s.autoComplete = false; // IE goes crazy if this isn't false @@ -256,7 +251,7 @@ Tagify.prototype = { else { DOM.originalInput = input DOM.scope = this.parseTemplate('wrapper', [input, this.settings]) - DOM.input = DOM.scope.querySelector(this.settings.classNames.inputSelector) + DOM.input = DOM.scope.querySelector(this.settings.inputSelector) input.parentNode.insertBefore(DOM.scope, input) } }, @@ -409,7 +404,7 @@ Tagify.prototype = { var _s = this.settings; function getEditableElm(){ - return tagElm.querySelector(_s.classNames.tagTextSelector) + return tagElm.querySelector(_s.tagTextSelector) } var editableElm = getEditableElm(), @@ -423,7 +418,7 @@ Tagify.prototype = { } if( !editableElm ){ - console.warn('Cannot find element in Tag template: .', _s.classNames.tagTextSelector); + console.warn('Cannot find element in Tag template: .', _s.tagTextSelector); return; } @@ -432,6 +427,7 @@ Tagify.prototype = { editableElm.setAttribute('contenteditable', true) tagElm.classList.add( _s.classNames.tagEditing ) + tagElm.dataset.tagifyTagStatus = 'editing' // cache the original data, on the DOM node, before any modification ocurs, for possible revert this.tagData(tagElm, { @@ -480,6 +476,8 @@ Tagify.prototype = { //this.validateTag(tagData); tagElm.classList.toggle(this.settings.classNames.tagNotAllowed, !isValid) + if(!isValid) + tagElm.dataset.tagifyTagStatus = 'notAllowed' return tagData.__isValid }, @@ -547,7 +545,7 @@ Tagify.prototype = { this.value.length = 0; [].forEach.call(this.getTagElms(), node => { - if( node.classList.contains(this.settings.classNames.tagNotAllowed.split(' ')[0]) ) return + if( node.dataset.tagifyTagStatus === 'notAllowed' ) return this.value.push( this.tagData(node) ) }) @@ -729,15 +727,18 @@ Tagify.prototype = { }, getTagElms( ...classess ){ - var classname = '.' + [...this.settings.classNames.tag.split(' '), ...classess].join('.') - return [].slice.call(this.DOM.scope.querySelectorAll(classname)) // convert nodeList to Array - https://stackoverflow.com/a/3199627/104380 + var selector = '' + if (classess !== undefined && classess.length > 0) + selector = '.' + classess.join('.') + selector += this.settings.tagSelector + return [].slice.call(this.DOM.scope.querySelectorAll(selector)) // convert nodeList to Array - https://stackoverflow.com/a/3199627/104380 }, /** - * gets the last non-readonly, not-in-the-proccess-of-removal tag + * gets the last non-readonly, not-in-the-process-of-removal tag */ getLastTag(){ - var lastTag = this.DOM.scope.querySelectorAll(`${this.settings.classNames.tagSelector}:not(.${this.settings.classNames.tagHide}):not([readonly])`); + var lastTag = this.DOM.scope.querySelectorAll(`${this.settings.tagSelector}:not([data-tagify-tag-status='hide']):not([readonly])`); return lastTag[lastTag.length - 1]; }, @@ -1414,7 +1415,7 @@ Tagify.prototype = { // if only a single tag is to be removed if( tagsToRemove.length == 1 ){ - if( tagsToRemove[0].node.classList.contains(this.settings.classNames.tagNotAllowed) ) + if( tagsToRemove[0].node.dataset.tagifyTagStatus === 'notAllowed' ) silent = true } @@ -1447,6 +1448,7 @@ Tagify.prototype = { tag.node.style.width = parseFloat(window.getComputedStyle(tag.node).width) + 'px' document.body.clientTop // force repaint for the width to take affect before the "hide" class below tag.node.classList.add(this.settings.classNames.tagHide) + tag.node.dataset.tagifyTagStatus = 'hide' // manual timeout (hack, since transitionend cannot be used because of hover) setTimeout(removeNode.bind(this), tranDuration, tag) @@ -1567,7 +1569,7 @@ Tagify.prototype = { function iterateChildren(rootNode){ rootNode.childNodes.forEach((node) => { if( node.nodeType == 1 ){ - if( node.classList.contains(that.settings.classNames.tag) && that.tagData(node) ){ + if( node.dataset.tagifyControl === 'tag' && that.tagData(node) ){ if( that.tagData(node).__removed ) return; else From d4c6c62bec58cdc076dd989c31952b352c5744d7 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Fri, 18 Jun 2021 23:42:46 +0200 Subject: [PATCH 2/2] Fix small issue --- src/tagify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tagify.js b/src/tagify.js index 56a8d868..bc17b3ba 100644 --- a/src/tagify.js +++ b/src/tagify.js @@ -728,7 +728,7 @@ Tagify.prototype = { getTagElms( ...classess ){ var selector = '' - if (classess !== undefined && classess.length > 0) + if (classess.length > 0) selector = '.' + classess.join('.') selector += this.settings.tagSelector return [].slice.call(this.DOM.scope.querySelectorAll(selector)) // convert nodeList to Array - https://stackoverflow.com/a/3199627/104380