@yaireo/tagify
Advanced tools
Comparing version 2.0.3 to 2.1.0
"use strict"; | ||
/** | ||
* Tagify (v 2.0.2)- tags input component | ||
* Tagify (v 2.0.4)- tags input component | ||
* By Yair Even-Or (2016) | ||
@@ -52,2 +52,3 @@ * Don't sell this code. (c) | ||
this.value = []; // An array holding all the (currently used) tags | ||
this.stringValue = ""; // same as above, only as a String | ||
@@ -68,13 +69,22 @@ // events' callbacks references will be stores here, so events could be unbinded | ||
TEXTS: { | ||
empty: "empty", | ||
exceed: "number of tags exceeded", | ||
pattern: "pattern mismatch", | ||
duplicate: "already exists", | ||
notAllowed: "not allowed" | ||
}, | ||
DEFAULTS: { | ||
delimiters: ",", // [regex] split tags by any of these delimiters ("null" to cancel) Example: ",| |." | ||
pattern: null, // regex pattern to validate input by. Ex: /[1-9]/ | ||
maxTags: Infinity, // maximum number of tags | ||
callbacks: {}, // exposed callbacks object to be triggered on certain events | ||
addTagOnBlur: true, // flag - automatically adds the text which was inputed as a tag when blur event happens | ||
duplicates: false, // flag - allow tuplicate tags | ||
whitelist: [], // is this list has any items, then only allow tags from this list | ||
blacklist: [], // a list of non-allowed tags | ||
enforceWhitelist: false, // flag - should ONLY use tags allowed in whitelist | ||
autoComplete: true, // flag - tries to autocomplete the input's value while typing | ||
delimiters: ",", // [RegEx] split tags by any of these delimiters ("null" to cancel) Example: ",| |." | ||
pattern: null, // RegEx pattern to validate input by. Ex: /[1-9]/ | ||
maxTags: Infinity, // Maximum number of tags | ||
callbacks: {}, // Exposed callbacks object to be triggered on certain events | ||
addTagOnBlur: true, // Flag - automatically adds the text which was inputed as a tag when blur event happens | ||
duplicates: false, // Flag - allow tuplicate tags | ||
whitelist: [], // Array of tags to suggest as the user types (can be used along with "enforceWhitelist" setting) | ||
blacklist: [], // A list of non-allowed tags | ||
enforceWhitelist: false, // Flag - Only allow tags allowed in whitelist | ||
keepInvalidTags: false, // Flag - if true, do not remove tags which did not pass validation | ||
autoComplete: true, // Flag - tries to autocomplete the input's value while typing | ||
mapValueToProp: "", // String - when tags have multiple properties, and for each tag another property should be used besides the "value" | ||
@@ -88,3 +98,3 @@ dropdown: { | ||
customEventsList: ['add', 'remove', 'duplicate', 'maxTagsExceed', 'blacklisted', 'notWhitelisted'], | ||
customEventsList: ['add', 'remove', 'invalid'], | ||
@@ -121,3 +131,3 @@ /** | ||
value = input.value, | ||
template = "\n <tags class=\"tagify " + input.className + " " + (this.settings.readonly ? 'readonly' : '') + "\">\n <div contenteditable data-placeholder=\"" + input.placeholder + "\" class=\"tagify--input\"></div>\n </tags>"; | ||
template = "\n <tags class=\"tagify " + input.className + " " + (this.settings.readonly ? 'readonly' : '') + "\">\n <div contenteditable data-placeholder=\"" + input.placeholder + "\" class=\"tagify__input\"></div>\n </tags>"; | ||
@@ -129,3 +139,2 @@ this.DOM.originalInput = input; | ||
// if "autocomplete" flag on toggeled & "whitelist" has items, build suggestions list | ||
if (this.settings.dropdown.enabled && this.settings.whitelist.length) { | ||
@@ -139,2 +148,4 @@ this.dropdown.init.call(this); | ||
}); | ||
input.autofocus && this.DOM.input.focus(); | ||
}, | ||
@@ -295,14 +306,22 @@ | ||
onInput: function onInput(e) { | ||
var value = e.target.textContent.trim(), | ||
var value = this.input.normalize.call(this), | ||
showSuggestions = value.length >= this.settings.dropdown.enabled; | ||
if (this.input.value == value) return; | ||
// save the value on the input state object | ||
this.input.value = value; | ||
this.input.normalize.call(this); | ||
this.input.autocomplete.suggest.call(this, ''); // cleanup any possible previous suggestion | ||
if (!value) { | ||
this.input.set.call(this, ''); | ||
return; | ||
} | ||
if (this.input.value == value) return; // for IE; since IE doesn't have an "input" event so "keyDown" is used instead | ||
// save the value on the input's State object | ||
this.input.set.call(this, value, false); | ||
if (value.search(this.settings.delimiters) != -1) { | ||
if (this.addTags(value).length) this.input.set.call(this); // clear the input field's value | ||
} else if (this.settings.dropdown.enabled && this.settings.whitelist.length) this.dropdown[showSuggestions ? "show" : "hide"].call(this, value); | ||
if (this.addTags(value).length) { | ||
this.input.set.call(this); // clear the input field's value | ||
} | ||
} else if (this.settings.dropdown.enabled && this.settings.whitelist.length) { | ||
this.dropdown[showSuggestions ? "show" : "hide"].call(this, value); | ||
} | ||
}, | ||
@@ -333,14 +352,34 @@ onInputIE: function onInputIE(e) { | ||
var s = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; | ||
var updateDOM = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; | ||
this.input.value = this.DOM.input.innerHTML = s; | ||
this.input.value = s; | ||
if (updateDOM) this.DOM.input.innerHTML = s; | ||
if (s.length < 2) this.input.autocomplete.suggest.call(this, ''); | ||
this.input.validate.call(this); | ||
}, | ||
/** | ||
* Marks the tagify's input as "invalid" if the value did not pass "validateTag()" | ||
*/ | ||
validate: function validate() { | ||
var isValid = !this.input.value || this.validateTag.call(this, this.input.value); | ||
this.DOM.input.classList.toggle('tagify__input--invalid', isValid !== true); | ||
}, | ||
// remove any child DOM elements that aren't of type TEXT (like <br>) | ||
normalize: function normalize() { | ||
while (this.DOM.input.firstElementChild) { | ||
this.DOM.input.removeChild(this.DOM.input.firstElementChild); | ||
var clone = this.DOM.input.cloneNode(true), | ||
v = clone.textContent.replace(/\s/g, ' '); // replace NBSPs with spaces characters | ||
while (clone.firstElementChild) { | ||
v += clone.firstElementChild.textContent; | ||
clone.removeChild(clone.firstElementChild); | ||
} | ||
return v.replace(/^\s+/, ""); // trimLeft | ||
}, | ||
@@ -382,5 +421,6 @@ | ||
isTagDuplicate: function isTagDuplicate(s) { | ||
return this.value.some(function (item) { | ||
return this.value.findIndex(function (item) { | ||
return s.toLowerCase() === item.value.toLowerCase(); | ||
}); | ||
// return this.value.some(item => s.toLowerCase() === item.value.toLowerCase()); | ||
}, | ||
@@ -396,9 +436,7 @@ | ||
markTagByValue: function markTagByValue(value, tagElm) { | ||
var tagsElms, tagsElmsLen; | ||
var tagsElms, tagsElmsLen, tagIdx; | ||
if (!tagElm) { | ||
tagsElms = this.DOM.scope.querySelectorAll('tag'); | ||
for (tagsElmsLen = tagsElms.length; tagsElmsLen--;) { | ||
if (tagsElms[tagsElmsLen].value.toLowerCase().includes(value.toLowerCase())) tagElm = tagsElms[tagsElmsLen]; | ||
} | ||
tagIdx = this.isTagDuplicate.call(this, value); | ||
tagElm = this.DOM.scope.querySelectorAll('tag')[tagIdx]; | ||
} | ||
@@ -411,5 +449,5 @@ | ||
tagElm.classList.remove('tagify--mark'); | ||
}, 2000); | ||
return true; | ||
} else {} | ||
}, 100); | ||
return tagElm; | ||
} | ||
@@ -443,2 +481,27 @@ return false; | ||
/** | ||
* validate a tag object BEFORE the actual tag will be created & appeneded | ||
* @param {String} s | ||
* @return {Boolean/String} ["true" if validation has passed, String for a fail] | ||
*/ | ||
validateTag: function validateTag(s) { | ||
var value = s.trim(), | ||
maxTagsExceed = this.value.length >= this.settings.maxTags, | ||
isDuplicate, | ||
eventName__error, | ||
result = true; | ||
// check for empty value | ||
if (!value) result = this.TEXTS.empty;else if (maxTagsExceed) result = this.TEXTS.exceed; | ||
// check if pattern should be used and if so, use it to test the value | ||
else if (this.settings.pattern && !this.settings.pattern.test(value)) result = this.TEXTS.pattern; | ||
// if duplicates are not allowed and there is a duplicate | ||
else if (!this.settings.duplicates && this.isTagDuplicate(value) !== -1) result = this.TEXTS.duplicate;else if (this.isTagBlacklisted(value) || this.settings.enforceWhitelist && !this.isTagWhitelisted(value)) result = this.TEXTS.notAllowed; | ||
return result; | ||
}, | ||
/** | ||
* add a "tag" element to the "tags" component | ||
@@ -450,5 +513,6 @@ * @param {String/Array} tagsItems [A string (single or multiple values with a delimiter), or an Array of Objects] | ||
addTags: function addTags(tagsItems, clearInput) { | ||
var that = this, | ||
tagElems = []; | ||
var _this3 = this; | ||
var tagElems = []; | ||
this.DOM.input.removeAttribute('style'); | ||
@@ -484,7 +548,8 @@ | ||
if (!isComplex) { | ||
tagsItems = tagsItems.trim(); | ||
if (!tagsItems) return []; | ||
if (!tagsItems.trim()) return []; | ||
// go over each tag and add it (if there were multiple ones) | ||
result = tagsItems.split(this.settings.delimiters).map(function (v) { | ||
result = tagsItems.split(this.settings.delimiters).filter(function (n) { | ||
return n; | ||
}).map(function (v) { | ||
return { value: v.trim() }; | ||
@@ -494,54 +559,6 @@ }); | ||
return result.filter(function (n) { | ||
return n; | ||
}); // cleanup the array from "undefined", "false" or empty items; | ||
return result; | ||
} | ||
/** | ||
* validate a tag object BEFORE the actual tag will be created & appeneded | ||
* @param {Object} tagData [{"value":"text", "class":whatever", ...}] | ||
* @return {Boolean/String} ["true" if validation has passed, String or "false" for any type of error] | ||
*/ | ||
function validateTag(tagData) { | ||
var value = tagData.value.trim(), | ||
maxTagsExceed = this.value.length >= this.settings.maxTags, | ||
isDuplicate, | ||
eventName__error, | ||
tagAllowed; | ||
// check for empty value | ||
if (!value) return "empty"; | ||
// check if pattern should be used and if so, use it to test the value | ||
if (this.settings.pattern && !this.settings.pattern.test(value)) return "pattern"; | ||
// check if the tag already exists | ||
if (this.isTagDuplicate(value)) { | ||
this.trigger('duplicate', value); | ||
if (!this.settings.duplicates) { | ||
// this.markTagByValue(value, tagElm) | ||
return "duplicate"; | ||
} | ||
} | ||
// check if the tag is allowed by the rules set | ||
tagAllowed = !this.isTagBlacklisted(value) && (!this.settings.enforceWhitelist || this.isTagWhitelisted(value)) && !maxTagsExceed; | ||
// Check against blacklist & whitelist (if enforced) | ||
if (!tagAllowed) { | ||
tagData.class = tagData.class ? tagData.class + " tagify--notAllowed" : "tagify--notAllowed"; | ||
// broadcast why the tag was not allowed | ||
if (maxTagsExceed) eventName__error = 'maxTagsExceed';else if (this.isTagBlacklisted(value)) eventName__error = 'blacklisted';else if (this.settings.enforceWhitelist && !this.isTagWhitelisted(value)) eventName__error = 'notWhitelisted'; | ||
this.trigger(eventName__error, { value: value, index: this.value.length }); | ||
return "notAllowed"; | ||
} | ||
return true; | ||
} | ||
/** | ||
* appened (validated) tag to the component's DOM scope | ||
@@ -558,24 +575,29 @@ * @return {[type]} [description] | ||
tagsItems.forEach(function (tagData) { | ||
var tagValidation = _this3.validateTag.call(_this3, tagData.value), | ||
tagElm; | ||
var isTagValidated = validateTag.call(that, tagData); | ||
if (isTagValidated === true || isTagValidated == "notAllowed") { | ||
// create the tag element | ||
var tagElm = that.createTagElem(tagData); | ||
if (tagValidation !== true) { | ||
tagData.class = tagData.class ? tagData.class + " tagify--notAllowed" : "tagify--notAllowed"; | ||
tagData.title = tagValidation; | ||
_this3.markTagByValue.call(_this3, tagData.value); | ||
_this3.trigger("invalid", { value: tagData.value, index: _this3.value.length, message: tagValidation }); | ||
} | ||
// add the tag to the component's DOM | ||
appendTag.call(that, tagElm); | ||
// Create tag HTML element | ||
tagElm = _this3.createTagElem(tagData); | ||
tagElems.push(tagElm); | ||
// remove the tag "slowly" | ||
if (isTagValidated == "notAllowed") { | ||
setTimeout(function () { | ||
that.removeTag(tagElm, true); | ||
}, 1000); | ||
} else { | ||
// update state | ||
that.value.push(tagData); | ||
that.update(); | ||
that.trigger('add', that.extend({}, { index: that.value.length, tag: tagElm }, tagData)); | ||
// add the tag to the component's DOM | ||
appendTag.call(_this3, tagElm); | ||
tagElems.push(tagElm); | ||
} | ||
if (tagValidation === true) { | ||
// update state | ||
_this3.value.push(tagData); | ||
_this3.update(); | ||
_this3.trigger('add', _this3.extend({}, { index: _this3.value.length, tag: tagElm }, tagData)); | ||
} else if (!_this3.settings.keepInvalidTags) { | ||
// remove invalid tags (if "keepInvalidTags" is set to "false") | ||
setTimeout(function () { | ||
_this3.removeTag(tagElm, true); | ||
}, 1000); | ||
} | ||
@@ -600,3 +622,3 @@ }); | ||
escapedValue = this.escapeHtml(tagData.value), | ||
template = "<tag>\n <x></x><div><span title='" + escapedValue + "'>" + escapedValue + "</span></div>\n </tag>"; | ||
template = "<tag title='" + escapedValue + "'>\n <x title=''></x><div><span>" + escapedValue + "</span></div>\n </tag>"; | ||
@@ -629,2 +651,4 @@ // for a certain Tag element, add attributes. | ||
removeTag: function removeTag(tagElm, silent) { | ||
if (!tagElm) return; | ||
var tagData, | ||
@@ -664,8 +688,9 @@ tagIdx = this.getNodeIndex(tagElm); | ||
update: function update() { | ||
var _this3 = this; | ||
var _this4 = this; | ||
var tagsAsString = this.value.map(function (v) { | ||
return v[_this3.settings.mapValueToProp || "value"] || v.value; | ||
return v[_this4.settings.mapValueToProp || "value"] || v.value; | ||
}); | ||
this.DOM.originalInput.value = JSON.stringify(tagsAsString).slice(1, -1); | ||
this.stringValue = JSON.stringify(tagsAsString).slice(1, -1); | ||
this.DOM.originalInput.value = this.stringValue; | ||
}, | ||
@@ -691,3 +716,5 @@ | ||
if (listItems.length && this.settings.autoComplete) this.input.autocomplete.suggest.call(this, listItems[0].value); | ||
if (this.settings.autoComplete) { | ||
this.input.autocomplete.suggest.call(this, listItems.length ? listItems[0].value : ''); | ||
} | ||
@@ -722,3 +749,3 @@ if (!listHTML || listItems.length < 2) { | ||
this.DOM.dropdown.style.cssText = "left: " + rect.left + window.pageXOffset + "px; \ | ||
this.DOM.dropdown.style.cssText = "left: " + (rect.left + window.pageXOffset) + "px; \ | ||
top: " + (rect.top + rect.height - 1 + window.pageYOffset) + "px; \ | ||
@@ -844,3 +871,3 @@ width: " + rect.width + "px"; | ||
// match for the value within each "whitelist" item | ||
if (valueIsInWhitelist && !this.isTagDuplicate(whitelistItem.value) && suggestionsCount--) list.push(whitelistItem); | ||
if (valueIsInWhitelist && this.isTagDuplicate(whitelistItem.value) == -1 && suggestionsCount--) list.push(whitelistItem); | ||
if (suggestionsCount == 0) break; | ||
@@ -847,0 +874,0 @@ } |
@@ -1,1 +0,1 @@ | ||
"use strict";!function(t){function e(t,e){if(!t)return console.warn("Tagify: ","invalid input element ",t),this;if(this.settings=this.extend({},this.DEFAULTS,e),this.settings.readonly=t.hasAttribute("readonly"),this.isIE&&(this.settings.autoComplete=!1),t.pattern)try{this.settings.pattern=new RegExp(t.pattern)}catch(t){}if(this.settings&&this.settings.delimiters)try{this.settings.delimiters=new RegExp("["+this.settings.delimiters+"]","g")}catch(t){}this.id=Math.random().toString(36).substr(2,9),this.value=[],this.listeners={},this.DOM={},this.extend(this,new this.EventDispatcher(this)),this.build(t),this.events.customBinding.call(this),this.events.binding.call(this)}t.fn.tagify=function(){var i=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return this.each(function(){var n,s=t(this);if(s.data("tagify"))return this;i.isJQueryPlugin=!0,n=new e(s[0],i),s.data("tagify",n)})},e.prototype={isIE:window.document.documentMode,DEFAULTS:{delimiters:",",pattern:null,maxTags:1/0,callbacks:{},addTagOnBlur:!0,duplicates:!1,whitelist:[],blacklist:[],enforceWhitelist:!1,autoComplete:!0,mapValueToProp:"",dropdown:{classname:"",enabled:2,maxItems:10}},customEventsList:["add","remove","duplicate","maxTagsExceed","blacklisted","notWhitelisted"],parseHTML:function(t){return(new DOMParser).parseFromString(t.trim(),"text/html").body.firstElementChild},escapeHtml:function(t){var e=document.createTextNode(t),i=document.createElement("p");return i.appendChild(e),i.innerHTML},build:function(t){var e=t.value,i='\n <tags class="tagify '+t.className+" "+(this.settings.readonly?"readonly":"")+'">\n <div contenteditable data-placeholder="'+t.placeholder+'" class="tagify--input"></div>\n </tags>';this.DOM.originalInput=t,this.DOM.scope=this.parseHTML(i),this.DOM.input=this.DOM.scope.querySelector("[contenteditable]"),t.parentNode.insertBefore(this.DOM.scope,t),this.settings.dropdown.enabled&&this.settings.whitelist.length&&this.dropdown.init.call(this),e&&this.addTags(e).forEach(function(t){t&&t.classList.add("tagify--noAnim")})},destroy:function(){this.DOM.scope.parentNode.removeChild(this.DOM.scope)},extend:function(t,e,i){function n(t){var e=Object.prototype.toString.call(t).split(" ")[1].slice(0,-1);return t===Object(t)&&"Array"!=e&&"Function"!=e&&"RegExp"!=e}function s(t,e){for(var i in e)e.hasOwnProperty(i)&&(n(e[i])?n(t[i])?s(t[i],e[i]):t[i]=Object.assign({},e[i]):t[i]=e[i])}return t instanceof Object||(t={}),s(t,e),i&&s(t,i),t},EventDispatcher:function(e){var i=document.createTextNode("");this.off=function(t,e){return e&&i.removeEventListener.call(i,t,e),this},this.on=function(t,e){return e&&i.addEventListener.call(i,t,e),this},this.trigger=function(n,s){var o;if(n)if(e.settings.isJQueryPlugin)t(e.DOM.originalInput).triggerHandler(n,[s]);else{try{o=new CustomEvent(n,{detail:s})}catch(t){console.warn(t)}i.dispatchEvent(o)}}},events:{customBinding:function(){var t=this;this.customEventsList.forEach(function(e){t.on(e,t.settings.callbacks[e])})},binding:function(){var e=!(arguments.length>0&&void 0!==arguments[0])||arguments[0],i=this.events.callbacks,n=this.listeners.main=this.listeners.main||{paste:["input",i.onPaste.bind(this)],focus:["input",i.onFocusBlur.bind(this)],blur:["input",i.onFocusBlur.bind(this)],keydown:["input",i.onKeydown.bind(this)],click:["scope",i.onClickScope.bind(this)]},s=e?"addEventListener":"removeEventListener";for(var o in n)this.DOM[n[o][0]][s](o,n[o][1]);e&&(this.DOM.input.addEventListener(this.isIE?"keydown":"input",i[this.isIE?"onInputIE":"onInput"].bind(this)),this.settings.isJQueryPlugin&&t(this.DOM.originalInput).on("tagify.removeAllTags",this.removeAllTags.bind(this)))},callbacks:{onFocusBlur:function(t){var e=t.target.textContent.trim();"focus"==t.type||("blur"==t.type&&e?this.settings.addTagOnBlur&&this.addTags(e,!0).length:(this.DOM.input.removeAttribute("style"),this.dropdown.hide.call(this)))},onKeydown:function(t){var e,i=t.target.textContent;"Backspace"!=t.key||""!=i&&8203!=i.charCodeAt(0)?"Escape"==t.key?(this.input.set.call(this),t.target.blur()):"Enter"==t.key?(t.preventDefault(),this.addTags(this.input.value,!0)):"ArrowRight"==t.key&&this.input.autocomplete.set.call(this):(e=(e=this.DOM.scope.querySelectorAll("tag:not(.tagify--hide)"))[e.length-1],this.removeTag(e))},onInput:function(t){var e=t.target.textContent.trim(),i=e.length>=this.settings.dropdown.enabled;this.input.value!=e&&(this.input.value=e,this.input.normalize.call(this),this.input.autocomplete.suggest.call(this,""),-1!=e.search(this.settings.delimiters)?this.addTags(e).length&&this.input.set.call(this):this.settings.dropdown.enabled&&this.settings.whitelist.length&&this.dropdown[i?"show":"hide"].call(this,e))},onInputIE:function(t){var e=this;setTimeout(function(){e.events.callbacks.onInput.call(e,t)})},onPaste:function(t){},onClickScope:function(t){"TAGS"==t.target.tagName?this.DOM.input.focus():"X"==t.target.tagName&&this.removeTag(t.target.parentNode)}}},input:{value:"",set:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"";this.input.value=this.DOM.input.innerHTML=t,t.length<2&&this.input.autocomplete.suggest.call(this,"")},normalize:function(){for(;this.DOM.input.firstElementChild;)this.DOM.input.removeChild(this.DOM.input.firstElementChild)},autocomplete:{suggest:function(t){t?this.DOM.input.setAttribute("data-suggest",t.substring(this.input.value.length)):this.DOM.input.removeAttribute("data-suggest")},set:function(){var t=this.DOM.input.getAttribute("data-suggest");t&&this.addTags(this.input.value+t).length&&(this.input.set.call(this),this.dropdown.hide.call(this))}}},getNodeIndex:function(t){for(var e=0;t=t.previousSibling;)3==t.nodeType&&/^\s*$/.test(t.data)||e++;return e},isTagDuplicate:function(t){return this.value.some(function(e){return t.toLowerCase()===e.value.toLowerCase()})},markTagByValue:function(t,e){var i,n;if(!e)for(n=(i=this.DOM.scope.querySelectorAll("tag")).length;n--;)i[n].value.toLowerCase().includes(t.toLowerCase())&&(e=i[n]);return!!e&&(e.classList.add("tagify--mark"),setTimeout(function(){e.classList.remove("tagify--mark")},2e3),!0)},isTagBlacklisted:function(t){return t=t.split(" "),this.settings.blacklist.filter(function(e){return-1!=t.indexOf(e)}).length},isTagWhitelisted:function(t){return this.settings.whitelist.some(function(e){if((e.value?e.value:e).toLowerCase()===t.toLowerCase())return!0})},addTags:function(t,e){function i(t){var e,i=t.value.trim(),n=this.value.length>=this.settings.maxTags;return i?this.settings.pattern&&!this.settings.pattern.test(i)?"pattern":this.isTagDuplicate(i)&&(this.trigger("duplicate",i),!this.settings.duplicates)?"duplicate":!!(!this.isTagBlacklisted(i)&&(!this.settings.enforceWhitelist||this.isTagWhitelisted(i))&&!n)||(t.class=t.class?t.class+" tagify--notAllowed":"tagify--notAllowed",n?e="maxTagsExceed":this.isTagBlacklisted(i)?e="blacklisted":this.settings.enforceWhitelist&&!this.isTagWhitelisted(i)&&(e="notWhitelisted"),this.trigger(e,{value:i,index:this.value.length}),"notAllowed"):"empty"}function n(t){this.DOM.scope.insertBefore(t,this.DOM.input)}var s=this,o=[];return this.DOM.input.removeAttribute("style"),(t=function(t){var e=this.settings.whitelist[0]instanceof Object,i=t instanceof Array&&"value"in t[0],n=t;if(i)return n;if(!i&&"string"==typeof t&&e){var s=this.settings.whitelist.filter(function(e){return e.value.toLowerCase()==t.toLowerCase()});s[0]&&(i=!0,n=s)}if(!i){if(!(t=t.trim()))return[];n=t.split(this.settings.delimiters).map(function(t){return{value:t.trim()}})}return n.filter(function(t){return t})}.call(this,t)).forEach(function(t){var e=i.call(s,t);if(!0===e||"notAllowed"==e){var a=s.createTagElem(t);n.call(s,a),"notAllowed"==e?setTimeout(function(){s.removeTag(a,!0)},1e3):(s.value.push(t),s.update(),s.trigger("add",s.extend({},{index:s.value.length,tag:a},t)),o.push(a))}}),t.length&&e&&this.input.set.call(this),o},createTagElem:function(t){var e,i=this.escapeHtml(t.value),n="<tag>\n <x></x><div><span title='"+i+"'>"+i+"</span></div>\n </tag>";return e=this.parseHTML(n),function(t,e){var i,n=Object.keys(e);for(i=n.length;i--;){var s=n[i];if(!e.hasOwnProperty(s))return;t.setAttribute(s,e[s])}}(e,t),e},removeTag:function(t,e){var i,n=this.getNodeIndex(t);t&&(t.style.width=parseFloat(window.getComputedStyle(t).width)+"px",document.body.clientTop,t.classList.add("tagify--hide"),setTimeout(function(){t.parentNode.removeChild(t)},400),e||(i=this.value.splice(n,1)[0],this.update(),this.trigger("remove",this.extend({},{index:n,tag:t},i))))},removeAllTags:function(){this.value=[],this.update(),Array.prototype.slice.call(this.DOM.scope.querySelectorAll("tag")).forEach(function(t){return t.parentNode.removeChild(t)})},update:function(){var t=this,e=this.value.map(function(e){return e[t.settings.mapValueToProp||"value"]||e.value});this.DOM.originalInput.value=JSON.stringify(e).slice(1,-1)},dropdown:{init:function(){this.DOM.dropdown=this.dropdown.build.call(this)},build:function(){var t='<div class="'+("tagify__dropdown "+this.settings.dropdown.classname).trim()+'"></div>';return this.parseHTML(t)},show:function(t){var e=this.dropdown.filterListItems.call(this,t),i=this.dropdown.createListHTML(e);e.length&&this.settings.autoComplete&&this.input.autocomplete.suggest.call(this,e[0].value),!i||e.length<2?this.dropdown.hide.call(this):(this.DOM.dropdown.innerHTML=i,this.dropdown.position.call(this),!this.DOM.dropdown.parentNode!=document.body&&(document.body.appendChild(this.DOM.dropdown),this.events.binding.call(this,!1),this.dropdown.events.binding.call(this)))},hide:function(){this.DOM.dropdown&&this.DOM.dropdown.parentNode==document.body&&(document.body.removeChild(this.DOM.dropdown),window.removeEventListener("resize",this.dropdown.position),this.dropdown.events.binding.call(this,!1),this.events.binding.call(this))},position:function(){var t=this.DOM.scope.getBoundingClientRect();this.DOM.dropdown.style.cssText="left: "+t.left+window.pageXOffset+"px; top: "+(t.top+t.height-1+window.pageYOffset)+"px; width: "+t.width+"px"},events:{binding:function(){var t=!(arguments.length>0&&void 0!==arguments[0])||arguments[0],e=this.listeners.dropdown=this.listeners.dropdown||{position:this.dropdown.position.bind(this),onKeyDown:this.dropdown.events.callbacks.onKeyDown.bind(this),onMouseOver:this.dropdown.events.callbacks.onMouseOver.bind(this),onClick:this.dropdown.events.callbacks.onClick.bind(this)},i=t?"addEventListener":"removeEventListener";window[i]("resize",e.position),window[i]("keydown",e.onKeyDown),window[i]("click",e.onClick),this.DOM.dropdown[i]("mouseover",e.onMouseOver)},callbacks:{onKeyDown:function(t){var e=this.DOM.dropdown.querySelectorAll("[class$='--active']")[0],i="";switch(t.key){case"ArrowDown":case"ArrowUp":case"Down":case"Up":t.preventDefault(),e=e?e["ArrowUp"==t.key||"Up"==t.key?"previousElementSibling":"nextElementSibling"]:this.DOM.dropdown.children["ArrowUp"==t.key||"Up"==t.key?this.DOM.dropdown.children.length-1:0],this.dropdown.highlightOption.call(this,e);break;case"Escape":this.dropdown.hide.call(this);break;case"Enter":t.preventDefault(),i=e?e.textContent:this.input.value,this.addTags(i,!0),this.dropdown.hide.call(this);break;case"ArrowRight":this.input.autocomplete.set.call(this)}},onMouseOver:function(t){t.target.className.includes("__item")&&this.dropdown.highlightOption.call(this,t.target)},onClick:function(t){t.target.className.includes("tagify__dropdown__item")&&(this.input.set.call(this),this.addTags(t.target.textContent)),this.dropdown.hide.call(this)}}},highlightOption:function(t){if(t){var e="tagify__dropdown__item--active";[].forEach.call(this.DOM.dropdown.querySelectorAll("[class$='--active']"),function(t){t.classList.remove(e)}),t.classList.add(e)}},filterListItems:function(t){if(!t)return"";for(var e,i=[],n=this.settings.whitelist,s=this.settings.dropdown.maxItems||1/0,o=0;o<n.length&&(e=n[o]instanceof Object?n[o]:{value:n[o]},0==e.value.toLowerCase().replace(/\s/g,"").indexOf(t.toLowerCase().replace(/\s/g,""))&&!this.isTagDuplicate(e.value)&&s--&&i.push(e),0!=s);o++);return i},createListHTML:function(t){function e(t){var e,i=Object.keys(t),n="";for(e=i.length;e--;){var s=i[e];if("class"!=s&&!t.hasOwnProperty(s))return;n+=" "+s+(t[s]?"="+t[s]:"")}return n}return t.map(function(t){return"<div class='tagify__dropdown__item "+(t.class?t.class:"")+"' "+e(t)+">"+t.value+"</div>"}).join("")}}}}(jQuery); | ||
"use strict";!function(t){function e(t,e){if(!t)return console.warn("Tagify: ","invalid input element ",t),this;if(this.settings=this.extend({},this.DEFAULTS,e),this.settings.readonly=t.hasAttribute("readonly"),this.isIE&&(this.settings.autoComplete=!1),t.pattern)try{this.settings.pattern=new RegExp(t.pattern)}catch(t){}if(this.settings&&this.settings.delimiters)try{this.settings.delimiters=new RegExp("["+this.settings.delimiters+"]","g")}catch(t){}this.id=Math.random().toString(36).substr(2,9),this.value=[],this.stringValue="",this.listeners={},this.DOM={},this.extend(this,new this.EventDispatcher(this)),this.build(t),this.events.customBinding.call(this),this.events.binding.call(this)}t.fn.tagify=function(){var i=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return this.each(function(){var n,s=t(this);if(s.data("tagify"))return this;i.isJQueryPlugin=!0,n=new e(s[0],i),s.data("tagify",n)})},e.prototype={isIE:window.document.documentMode,TEXTS:{empty:"empty",exceed:"number of tags exceeded",pattern:"pattern mismatch",duplicate:"already exists",notAllowed:"not allowed"},DEFAULTS:{delimiters:",",pattern:null,maxTags:1/0,callbacks:{},addTagOnBlur:!0,duplicates:!1,whitelist:[],blacklist:[],enforceWhitelist:!1,keepInvalidTags:!1,autoComplete:!0,mapValueToProp:"",dropdown:{classname:"",enabled:2,maxItems:10}},customEventsList:["add","remove","invalid"],parseHTML:function(t){return(new DOMParser).parseFromString(t.trim(),"text/html").body.firstElementChild},escapeHtml:function(t){var e=document.createTextNode(t),i=document.createElement("p");return i.appendChild(e),i.innerHTML},build:function(t){var e=t.value,i='\n <tags class="tagify '+t.className+" "+(this.settings.readonly?"readonly":"")+'">\n <div contenteditable data-placeholder="'+t.placeholder+'" class="tagify__input"></div>\n </tags>';this.DOM.originalInput=t,this.DOM.scope=this.parseHTML(i),this.DOM.input=this.DOM.scope.querySelector("[contenteditable]"),t.parentNode.insertBefore(this.DOM.scope,t),this.settings.dropdown.enabled&&this.settings.whitelist.length&&this.dropdown.init.call(this),e&&this.addTags(e).forEach(function(t){t&&t.classList.add("tagify--noAnim")}),t.autofocus&&this.DOM.input.focus()},destroy:function(){this.DOM.scope.parentNode.removeChild(this.DOM.scope)},extend:function(t,e,i){function n(t){var e=Object.prototype.toString.call(t).split(" ")[1].slice(0,-1);return t===Object(t)&&"Array"!=e&&"Function"!=e&&"RegExp"!=e}function s(t,e){for(var i in e)e.hasOwnProperty(i)&&(n(e[i])?n(t[i])?s(t[i],e[i]):t[i]=Object.assign({},e[i]):t[i]=e[i])}return t instanceof Object||(t={}),s(t,e),i&&s(t,i),t},EventDispatcher:function(e){var i=document.createTextNode("");this.off=function(t,e){return e&&i.removeEventListener.call(i,t,e),this},this.on=function(t,e){return e&&i.addEventListener.call(i,t,e),this},this.trigger=function(n,s){var a;if(n)if(e.settings.isJQueryPlugin)t(e.DOM.originalInput).triggerHandler(n,[s]);else{try{a=new CustomEvent(n,{detail:s})}catch(t){console.warn(t)}i.dispatchEvent(a)}}},events:{customBinding:function(){var t=this;this.customEventsList.forEach(function(e){t.on(e,t.settings.callbacks[e])})},binding:function(){var e=!(arguments.length>0&&void 0!==arguments[0])||arguments[0],i=this.events.callbacks,n=this.listeners.main=this.listeners.main||{paste:["input",i.onPaste.bind(this)],focus:["input",i.onFocusBlur.bind(this)],blur:["input",i.onFocusBlur.bind(this)],keydown:["input",i.onKeydown.bind(this)],click:["scope",i.onClickScope.bind(this)]},s=e?"addEventListener":"removeEventListener";for(var a in n)this.DOM[n[a][0]][s](a,n[a][1]);e&&(this.DOM.input.addEventListener(this.isIE?"keydown":"input",i[this.isIE?"onInputIE":"onInput"].bind(this)),this.settings.isJQueryPlugin&&t(this.DOM.originalInput).on("tagify.removeAllTags",this.removeAllTags.bind(this)))},callbacks:{onFocusBlur:function(t){var e=t.target.textContent.trim();"focus"==t.type||("blur"==t.type&&e?this.settings.addTagOnBlur&&this.addTags(e,!0).length:(this.DOM.input.removeAttribute("style"),this.dropdown.hide.call(this)))},onKeydown:function(t){var e,i=t.target.textContent;"Backspace"!=t.key||""!=i&&8203!=i.charCodeAt(0)?"Escape"==t.key?(this.input.set.call(this),t.target.blur()):"Enter"==t.key?(t.preventDefault(),this.addTags(this.input.value,!0)):"ArrowRight"==t.key&&this.input.autocomplete.set.call(this):(e=(e=this.DOM.scope.querySelectorAll("tag:not(.tagify--hide)"))[e.length-1],this.removeTag(e))},onInput:function(t){var e=this.input.normalize.call(this),i=e.length>=this.settings.dropdown.enabled;e?this.input.value!=e&&(this.input.set.call(this,e,!1),-1!=e.search(this.settings.delimiters)?this.addTags(e).length&&this.input.set.call(this):this.settings.dropdown.enabled&&this.settings.whitelist.length&&this.dropdown[i?"show":"hide"].call(this,e)):this.input.set.call(this,"")},onInputIE:function(t){var e=this;setTimeout(function(){e.events.callbacks.onInput.call(e,t)})},onPaste:function(t){},onClickScope:function(t){"TAGS"==t.target.tagName?this.DOM.input.focus():"X"==t.target.tagName&&this.removeTag(t.target.parentNode)}}},input:{value:"",set:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",e=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];this.input.value=t,e&&(this.DOM.input.innerHTML=t),t.length<2&&this.input.autocomplete.suggest.call(this,""),this.input.validate.call(this)},validate:function(){var t=!this.input.value||this.validateTag.call(this,this.input.value);this.DOM.input.classList.toggle("tagify__input--invalid",!0!==t)},normalize:function(){for(var t=this.DOM.input.cloneNode(!0),e=t.textContent.replace(/\s/g," ");t.firstElementChild;)e+=t.firstElementChild.textContent,t.removeChild(t.firstElementChild);return e.replace(/^\s+/,"")},autocomplete:{suggest:function(t){t?this.DOM.input.setAttribute("data-suggest",t.substring(this.input.value.length)):this.DOM.input.removeAttribute("data-suggest")},set:function(){var t=this.DOM.input.getAttribute("data-suggest");t&&this.addTags(this.input.value+t).length&&(this.input.set.call(this),this.dropdown.hide.call(this))}}},getNodeIndex:function(t){for(var e=0;t=t.previousSibling;)3==t.nodeType&&/^\s*$/.test(t.data)||e++;return e},isTagDuplicate:function(t){return this.value.findIndex(function(e){return t.toLowerCase()===e.value.toLowerCase()})},markTagByValue:function(t,e){var i;return e||(i=this.isTagDuplicate.call(this,t),e=this.DOM.scope.querySelectorAll("tag")[i]),!!e&&(e.classList.add("tagify--mark"),setTimeout(function(){e.classList.remove("tagify--mark")},100),e)},isTagBlacklisted:function(t){return t=t.split(" "),this.settings.blacklist.filter(function(e){return-1!=t.indexOf(e)}).length},isTagWhitelisted:function(t){return this.settings.whitelist.some(function(e){if((e.value?e.value:e).toLowerCase()===t.toLowerCase())return!0})},validateTag:function(t){var e=t.trim(),i=this.value.length>=this.settings.maxTags,n=!0;return e?i?n=this.TEXTS.exceed:this.settings.pattern&&!this.settings.pattern.test(e)?n=this.TEXTS.pattern:this.settings.duplicates||-1===this.isTagDuplicate(e)?(this.isTagBlacklisted(e)||this.settings.enforceWhitelist&&!this.isTagWhitelisted(e))&&(n=this.TEXTS.notAllowed):n=this.TEXTS.duplicate:n=this.TEXTS.empty,n},addTags:function(t,e){function i(t){this.DOM.scope.insertBefore(t,this.DOM.input)}var n=this,s=[];return this.DOM.input.removeAttribute("style"),(t=function(t){var e=this.settings.whitelist[0]instanceof Object,i=t instanceof Array&&"value"in t[0],n=t;if(i)return n;if(!i&&"string"==typeof t&&e){var s=this.settings.whitelist.filter(function(e){return e.value.toLowerCase()==t.toLowerCase()});s[0]&&(i=!0,n=s)}if(!i){if(!t.trim())return[];n=t.split(this.settings.delimiters).filter(function(t){return t}).map(function(t){return{value:t.trim()}})}return n}.call(this,t)).forEach(function(t){var e,a=n.validateTag.call(n,t.value);!0!==a&&(t.class=t.class?t.class+" tagify--notAllowed":"tagify--notAllowed",t.title=a,n.markTagByValue.call(n,t.value),n.trigger("invalid",{value:t.value,index:n.value.length,message:a})),e=n.createTagElem(t),s.push(e),i.call(n,e),!0===a?(n.value.push(t),n.update(),n.trigger("add",n.extend({},{index:n.value.length,tag:e},t))):n.settings.keepInvalidTags||setTimeout(function(){n.removeTag(e,!0)},1e3)}),t.length&&e&&this.input.set.call(this),s},createTagElem:function(t){var e,i=this.escapeHtml(t.value),n="<tag title='"+i+"'>\n <x title=''></x><div><span>"+i+"</span></div>\n </tag>";return e=this.parseHTML(n),function(t,e){var i,n=Object.keys(e);for(i=n.length;i--;){var s=n[i];if(!e.hasOwnProperty(s))return;t.setAttribute(s,e[s])}}(e,t),e},removeTag:function(t,e){if(t){var i,n=this.getNodeIndex(t);t&&(t.style.width=parseFloat(window.getComputedStyle(t).width)+"px",document.body.clientTop,t.classList.add("tagify--hide"),setTimeout(function(){t.parentNode.removeChild(t)},400),e||(i=this.value.splice(n,1)[0],this.update(),this.trigger("remove",this.extend({},{index:n,tag:t},i))))}},removeAllTags:function(){this.value=[],this.update(),Array.prototype.slice.call(this.DOM.scope.querySelectorAll("tag")).forEach(function(t){return t.parentNode.removeChild(t)})},update:function(){var t=this,e=this.value.map(function(e){return e[t.settings.mapValueToProp||"value"]||e.value});this.stringValue=JSON.stringify(e).slice(1,-1),this.DOM.originalInput.value=this.stringValue},dropdown:{init:function(){this.DOM.dropdown=this.dropdown.build.call(this)},build:function(){var t='<div class="'+("tagify__dropdown "+this.settings.dropdown.classname).trim()+'"></div>';return this.parseHTML(t)},show:function(t){var e=this.dropdown.filterListItems.call(this,t),i=this.dropdown.createListHTML(e);this.settings.autoComplete&&this.input.autocomplete.suggest.call(this,e.length?e[0].value:""),!i||e.length<2?this.dropdown.hide.call(this):(this.DOM.dropdown.innerHTML=i,this.dropdown.position.call(this),!this.DOM.dropdown.parentNode!=document.body&&(document.body.appendChild(this.DOM.dropdown),this.events.binding.call(this,!1),this.dropdown.events.binding.call(this)))},hide:function(){this.DOM.dropdown&&this.DOM.dropdown.parentNode==document.body&&(document.body.removeChild(this.DOM.dropdown),window.removeEventListener("resize",this.dropdown.position),this.dropdown.events.binding.call(this,!1),this.events.binding.call(this))},position:function(){var t=this.DOM.scope.getBoundingClientRect();this.DOM.dropdown.style.cssText="left: "+(t.left+window.pageXOffset)+"px; top: "+(t.top+t.height-1+window.pageYOffset)+"px; width: "+t.width+"px"},events:{binding:function(){var t=!(arguments.length>0&&void 0!==arguments[0])||arguments[0],e=this.listeners.dropdown=this.listeners.dropdown||{position:this.dropdown.position.bind(this),onKeyDown:this.dropdown.events.callbacks.onKeyDown.bind(this),onMouseOver:this.dropdown.events.callbacks.onMouseOver.bind(this),onClick:this.dropdown.events.callbacks.onClick.bind(this)},i=t?"addEventListener":"removeEventListener";window[i]("resize",e.position),window[i]("keydown",e.onKeyDown),window[i]("click",e.onClick),this.DOM.dropdown[i]("mouseover",e.onMouseOver)},callbacks:{onKeyDown:function(t){var e=this.DOM.dropdown.querySelectorAll("[class$='--active']")[0],i="";switch(t.key){case"ArrowDown":case"ArrowUp":case"Down":case"Up":t.preventDefault(),e=e?e["ArrowUp"==t.key||"Up"==t.key?"previousElementSibling":"nextElementSibling"]:this.DOM.dropdown.children["ArrowUp"==t.key||"Up"==t.key?this.DOM.dropdown.children.length-1:0],this.dropdown.highlightOption.call(this,e);break;case"Escape":this.dropdown.hide.call(this);break;case"Enter":t.preventDefault(),i=e?e.textContent:this.input.value,this.addTags(i,!0),this.dropdown.hide.call(this);break;case"ArrowRight":this.input.autocomplete.set.call(this)}},onMouseOver:function(t){t.target.className.includes("__item")&&this.dropdown.highlightOption.call(this,t.target)},onClick:function(t){t.target.className.includes("tagify__dropdown__item")&&(this.input.set.call(this),this.addTags(t.target.textContent)),this.dropdown.hide.call(this)}}},highlightOption:function(t){if(t){var e="tagify__dropdown__item--active";[].forEach.call(this.DOM.dropdown.querySelectorAll("[class$='--active']"),function(t){t.classList.remove(e)}),t.classList.add(e)}},filterListItems:function(t){if(!t)return"";for(var e,i=[],n=this.settings.whitelist,s=this.settings.dropdown.maxItems||1/0,a=0;a<n.length&&(e=n[a]instanceof Object?n[a]:{value:n[a]},0==e.value.toLowerCase().replace(/\s/g,"").indexOf(t.toLowerCase().replace(/\s/g,""))&&-1==this.isTagDuplicate(e.value)&&s--&&i.push(e),0!=s);a++);return i},createListHTML:function(t){function e(t){var e,i=Object.keys(t),n="";for(e=i.length;e--;){var s=i[e];if("class"!=s&&!t.hasOwnProperty(s))return;n+=" "+s+(t[s]?"="+t[s]:"")}return n}return t.map(function(t){return"<div class='tagify__dropdown__item "+(t.class?t.class:"")+"' "+e(t)+">"+t.value+"</div>"}).join("")}}}}(jQuery); |
/** | ||
* Tagify (v 2.0.2)- tags input component | ||
* Tagify (v 2.0.4)- tags input component | ||
* By Yair Even-Or (2016) | ||
@@ -43,2 +43,3 @@ * Don't sell this code. (c) | ||
this.value = []; // An array holding all the (currently used) tags | ||
this.stringValue = ""; // same as above, only as a String | ||
@@ -59,13 +60,22 @@ // events' callbacks references will be stores here, so events could be unbinded | ||
TEXTS: { | ||
empty: "empty", | ||
exceed: "number of tags exceeded", | ||
pattern: "pattern mismatch", | ||
duplicate: "already exists", | ||
notAllowed: "not allowed" | ||
}, | ||
DEFAULTS: { | ||
delimiters: ",", // [regex] split tags by any of these delimiters ("null" to cancel) Example: ",| |." | ||
pattern: null, // regex pattern to validate input by. Ex: /[1-9]/ | ||
maxTags: Infinity, // maximum number of tags | ||
callbacks: {}, // exposed callbacks object to be triggered on certain events | ||
addTagOnBlur: true, // flag - automatically adds the text which was inputed as a tag when blur event happens | ||
duplicates: false, // flag - allow tuplicate tags | ||
whitelist: [], // is this list has any items, then only allow tags from this list | ||
blacklist: [], // a list of non-allowed tags | ||
enforceWhitelist: false, // flag - should ONLY use tags allowed in whitelist | ||
autoComplete: true, // flag - tries to autocomplete the input's value while typing | ||
delimiters: ",", // [RegEx] split tags by any of these delimiters ("null" to cancel) Example: ",| |." | ||
pattern: null, // RegEx pattern to validate input by. Ex: /[1-9]/ | ||
maxTags: Infinity, // Maximum number of tags | ||
callbacks: {}, // Exposed callbacks object to be triggered on certain events | ||
addTagOnBlur: true, // Flag - automatically adds the text which was inputed as a tag when blur event happens | ||
duplicates: false, // Flag - allow tuplicate tags | ||
whitelist: [], // Array of tags to suggest as the user types (can be used along with "enforceWhitelist" setting) | ||
blacklist: [], // A list of non-allowed tags | ||
enforceWhitelist: false, // Flag - Only allow tags allowed in whitelist | ||
keepInvalidTags: false, // Flag - if true, do not remove tags which did not pass validation | ||
autoComplete: true, // Flag - tries to autocomplete the input's value while typing | ||
mapValueToProp: "", // String - when tags have multiple properties, and for each tag another property should be used besides the "value" | ||
@@ -79,3 +89,3 @@ dropdown: { | ||
customEventsList: ['add', 'remove', 'duplicate', 'maxTagsExceed', 'blacklisted', 'notWhitelisted'], | ||
customEventsList: ['add', 'remove', 'invalid'], | ||
@@ -112,3 +122,3 @@ /** | ||
value = input.value, | ||
template = '\n <tags class="tagify ' + input.className + ' ' + (this.settings.readonly ? 'readonly' : '') + '">\n <div contenteditable data-placeholder="' + input.placeholder + '" class="tagify--input"></div>\n </tags>'; | ||
template = '\n <tags class="tagify ' + input.className + ' ' + (this.settings.readonly ? 'readonly' : '') + '">\n <div contenteditable data-placeholder="' + input.placeholder + '" class="tagify__input"></div>\n </tags>'; | ||
@@ -120,3 +130,2 @@ this.DOM.originalInput = input; | ||
// if "autocomplete" flag on toggeled & "whitelist" has items, build suggestions list | ||
if (this.settings.dropdown.enabled && this.settings.whitelist.length) { | ||
@@ -130,2 +139,4 @@ this.dropdown.init.call(this); | ||
}); | ||
input.autofocus && this.DOM.input.focus(); | ||
}, | ||
@@ -286,14 +297,22 @@ | ||
onInput: function onInput(e) { | ||
var value = e.target.textContent.trim(), | ||
var value = this.input.normalize.call(this), | ||
showSuggestions = value.length >= this.settings.dropdown.enabled; | ||
if (this.input.value == value) return; | ||
// save the value on the input state object | ||
this.input.value = value; | ||
this.input.normalize.call(this); | ||
this.input.autocomplete.suggest.call(this, ''); // cleanup any possible previous suggestion | ||
if (!value) { | ||
this.input.set.call(this, ''); | ||
return; | ||
} | ||
if (this.input.value == value) return; // for IE; since IE doesn't have an "input" event so "keyDown" is used instead | ||
// save the value on the input's State object | ||
this.input.set.call(this, value, false); | ||
if (value.search(this.settings.delimiters) != -1) { | ||
if (this.addTags(value).length) this.input.set.call(this); // clear the input field's value | ||
} else if (this.settings.dropdown.enabled && this.settings.whitelist.length) this.dropdown[showSuggestions ? "show" : "hide"].call(this, value); | ||
if (this.addTags(value).length) { | ||
this.input.set.call(this); // clear the input field's value | ||
} | ||
} else if (this.settings.dropdown.enabled && this.settings.whitelist.length) { | ||
this.dropdown[showSuggestions ? "show" : "hide"].call(this, value); | ||
} | ||
}, | ||
@@ -324,14 +343,34 @@ onInputIE: function onInputIE(e) { | ||
var s = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; | ||
var updateDOM = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; | ||
this.input.value = this.DOM.input.innerHTML = s; | ||
this.input.value = s; | ||
if (updateDOM) this.DOM.input.innerHTML = s; | ||
if (s.length < 2) this.input.autocomplete.suggest.call(this, ''); | ||
this.input.validate.call(this); | ||
}, | ||
/** | ||
* Marks the tagify's input as "invalid" if the value did not pass "validateTag()" | ||
*/ | ||
validate: function validate() { | ||
var isValid = !this.input.value || this.validateTag.call(this, this.input.value); | ||
this.DOM.input.classList.toggle('tagify__input--invalid', isValid !== true); | ||
}, | ||
// remove any child DOM elements that aren't of type TEXT (like <br>) | ||
normalize: function normalize() { | ||
while (this.DOM.input.firstElementChild) { | ||
this.DOM.input.removeChild(this.DOM.input.firstElementChild); | ||
var clone = this.DOM.input.cloneNode(true), | ||
v = clone.textContent.replace(/\s/g, ' '); // replace NBSPs with spaces characters | ||
while (clone.firstElementChild) { | ||
v += clone.firstElementChild.textContent; | ||
clone.removeChild(clone.firstElementChild); | ||
} | ||
return v.replace(/^\s+/, ""); // trimLeft | ||
}, | ||
@@ -373,5 +412,6 @@ | ||
isTagDuplicate: function isTagDuplicate(s) { | ||
return this.value.some(function (item) { | ||
return this.value.findIndex(function (item) { | ||
return s.toLowerCase() === item.value.toLowerCase(); | ||
}); | ||
// return this.value.some(item => s.toLowerCase() === item.value.toLowerCase()); | ||
}, | ||
@@ -387,9 +427,7 @@ | ||
markTagByValue: function markTagByValue(value, tagElm) { | ||
var tagsElms, tagsElmsLen; | ||
var tagsElms, tagsElmsLen, tagIdx; | ||
if (!tagElm) { | ||
tagsElms = this.DOM.scope.querySelectorAll('tag'); | ||
for (tagsElmsLen = tagsElms.length; tagsElmsLen--;) { | ||
if (tagsElms[tagsElmsLen].value.toLowerCase().includes(value.toLowerCase())) tagElm = tagsElms[tagsElmsLen]; | ||
} | ||
tagIdx = this.isTagDuplicate.call(this, value); | ||
tagElm = this.DOM.scope.querySelectorAll('tag')[tagIdx]; | ||
} | ||
@@ -402,5 +440,5 @@ | ||
tagElm.classList.remove('tagify--mark'); | ||
}, 2000); | ||
return true; | ||
} else {} | ||
}, 100); | ||
return tagElm; | ||
} | ||
@@ -434,2 +472,27 @@ return false; | ||
/** | ||
* validate a tag object BEFORE the actual tag will be created & appeneded | ||
* @param {String} s | ||
* @return {Boolean/String} ["true" if validation has passed, String for a fail] | ||
*/ | ||
validateTag: function validateTag(s) { | ||
var value = s.trim(), | ||
maxTagsExceed = this.value.length >= this.settings.maxTags, | ||
isDuplicate, | ||
eventName__error, | ||
result = true; | ||
// check for empty value | ||
if (!value) result = this.TEXTS.empty;else if (maxTagsExceed) result = this.TEXTS.exceed; | ||
// check if pattern should be used and if so, use it to test the value | ||
else if (this.settings.pattern && !this.settings.pattern.test(value)) result = this.TEXTS.pattern; | ||
// if duplicates are not allowed and there is a duplicate | ||
else if (!this.settings.duplicates && this.isTagDuplicate(value) !== -1) result = this.TEXTS.duplicate;else if (this.isTagBlacklisted(value) || this.settings.enforceWhitelist && !this.isTagWhitelisted(value)) result = this.TEXTS.notAllowed; | ||
return result; | ||
}, | ||
/** | ||
* add a "tag" element to the "tags" component | ||
@@ -441,5 +504,6 @@ * @param {String/Array} tagsItems [A string (single or multiple values with a delimiter), or an Array of Objects] | ||
addTags: function addTags(tagsItems, clearInput) { | ||
var that = this, | ||
tagElems = []; | ||
var _this3 = this; | ||
var tagElems = []; | ||
this.DOM.input.removeAttribute('style'); | ||
@@ -475,7 +539,8 @@ | ||
if (!isComplex) { | ||
tagsItems = tagsItems.trim(); | ||
if (!tagsItems) return []; | ||
if (!tagsItems.trim()) return []; | ||
// go over each tag and add it (if there were multiple ones) | ||
result = tagsItems.split(this.settings.delimiters).map(function (v) { | ||
result = tagsItems.split(this.settings.delimiters).filter(function (n) { | ||
return n; | ||
}).map(function (v) { | ||
return { value: v.trim() }; | ||
@@ -485,54 +550,6 @@ }); | ||
return result.filter(function (n) { | ||
return n; | ||
}); // cleanup the array from "undefined", "false" or empty items; | ||
return result; | ||
} | ||
/** | ||
* validate a tag object BEFORE the actual tag will be created & appeneded | ||
* @param {Object} tagData [{"value":"text", "class":whatever", ...}] | ||
* @return {Boolean/String} ["true" if validation has passed, String or "false" for any type of error] | ||
*/ | ||
function validateTag(tagData) { | ||
var value = tagData.value.trim(), | ||
maxTagsExceed = this.value.length >= this.settings.maxTags, | ||
isDuplicate, | ||
eventName__error, | ||
tagAllowed; | ||
// check for empty value | ||
if (!value) return "empty"; | ||
// check if pattern should be used and if so, use it to test the value | ||
if (this.settings.pattern && !this.settings.pattern.test(value)) return "pattern"; | ||
// check if the tag already exists | ||
if (this.isTagDuplicate(value)) { | ||
this.trigger('duplicate', value); | ||
if (!this.settings.duplicates) { | ||
// this.markTagByValue(value, tagElm) | ||
return "duplicate"; | ||
} | ||
} | ||
// check if the tag is allowed by the rules set | ||
tagAllowed = !this.isTagBlacklisted(value) && (!this.settings.enforceWhitelist || this.isTagWhitelisted(value)) && !maxTagsExceed; | ||
// Check against blacklist & whitelist (if enforced) | ||
if (!tagAllowed) { | ||
tagData.class = tagData.class ? tagData.class + " tagify--notAllowed" : "tagify--notAllowed"; | ||
// broadcast why the tag was not allowed | ||
if (maxTagsExceed) eventName__error = 'maxTagsExceed';else if (this.isTagBlacklisted(value)) eventName__error = 'blacklisted';else if (this.settings.enforceWhitelist && !this.isTagWhitelisted(value)) eventName__error = 'notWhitelisted'; | ||
this.trigger(eventName__error, { value: value, index: this.value.length }); | ||
return "notAllowed"; | ||
} | ||
return true; | ||
} | ||
/** | ||
* appened (validated) tag to the component's DOM scope | ||
@@ -549,24 +566,29 @@ * @return {[type]} [description] | ||
tagsItems.forEach(function (tagData) { | ||
var tagValidation = _this3.validateTag.call(_this3, tagData.value), | ||
tagElm; | ||
var isTagValidated = validateTag.call(that, tagData); | ||
if (isTagValidated === true || isTagValidated == "notAllowed") { | ||
// create the tag element | ||
var tagElm = that.createTagElem(tagData); | ||
if (tagValidation !== true) { | ||
tagData.class = tagData.class ? tagData.class + " tagify--notAllowed" : "tagify--notAllowed"; | ||
tagData.title = tagValidation; | ||
_this3.markTagByValue.call(_this3, tagData.value); | ||
_this3.trigger("invalid", { value: tagData.value, index: _this3.value.length, message: tagValidation }); | ||
} | ||
// add the tag to the component's DOM | ||
appendTag.call(that, tagElm); | ||
// Create tag HTML element | ||
tagElm = _this3.createTagElem(tagData); | ||
tagElems.push(tagElm); | ||
// remove the tag "slowly" | ||
if (isTagValidated == "notAllowed") { | ||
setTimeout(function () { | ||
that.removeTag(tagElm, true); | ||
}, 1000); | ||
} else { | ||
// update state | ||
that.value.push(tagData); | ||
that.update(); | ||
that.trigger('add', that.extend({}, { index: that.value.length, tag: tagElm }, tagData)); | ||
// add the tag to the component's DOM | ||
appendTag.call(_this3, tagElm); | ||
tagElems.push(tagElm); | ||
} | ||
if (tagValidation === true) { | ||
// update state | ||
_this3.value.push(tagData); | ||
_this3.update(); | ||
_this3.trigger('add', _this3.extend({}, { index: _this3.value.length, tag: tagElm }, tagData)); | ||
} else if (!_this3.settings.keepInvalidTags) { | ||
// remove invalid tags (if "keepInvalidTags" is set to "false") | ||
setTimeout(function () { | ||
_this3.removeTag(tagElm, true); | ||
}, 1000); | ||
} | ||
@@ -591,3 +613,3 @@ }); | ||
escapedValue = this.escapeHtml(tagData.value), | ||
template = '<tag>\n <x></x><div><span title=\'' + escapedValue + '\'>' + escapedValue + '</span></div>\n </tag>'; | ||
template = '<tag title=\'' + escapedValue + '\'>\n <x title=\'\'></x><div><span>' + escapedValue + '</span></div>\n </tag>'; | ||
@@ -620,2 +642,4 @@ // for a certain Tag element, add attributes. | ||
removeTag: function removeTag(tagElm, silent) { | ||
if (!tagElm) return; | ||
var tagData, | ||
@@ -655,8 +679,9 @@ tagIdx = this.getNodeIndex(tagElm); | ||
update: function update() { | ||
var _this3 = this; | ||
var _this4 = this; | ||
var tagsAsString = this.value.map(function (v) { | ||
return v[_this3.settings.mapValueToProp || "value"] || v.value; | ||
return v[_this4.settings.mapValueToProp || "value"] || v.value; | ||
}); | ||
this.DOM.originalInput.value = JSON.stringify(tagsAsString).slice(1, -1); | ||
this.stringValue = JSON.stringify(tagsAsString).slice(1, -1); | ||
this.DOM.originalInput.value = this.stringValue; | ||
}, | ||
@@ -682,3 +707,5 @@ | ||
if (listItems.length && this.settings.autoComplete) this.input.autocomplete.suggest.call(this, listItems[0].value); | ||
if (this.settings.autoComplete) { | ||
this.input.autocomplete.suggest.call(this, listItems.length ? listItems[0].value : ''); | ||
} | ||
@@ -713,3 +740,3 @@ if (!listHTML || listItems.length < 2) { | ||
this.DOM.dropdown.style.cssText = "left: " + rect.left + window.pageXOffset + "px; \ | ||
this.DOM.dropdown.style.cssText = "left: " + (rect.left + window.pageXOffset) + "px; \ | ||
top: " + (rect.top + rect.height - 1 + window.pageYOffset) + "px; \ | ||
@@ -835,3 +862,3 @@ width: " + rect.width + "px"; | ||
// match for the value within each "whitelist" item | ||
if (valueIsInWhitelist && !this.isTagDuplicate(whitelistItem.value) && suggestionsCount--) list.push(whitelistItem); | ||
if (valueIsInWhitelist && this.isTagDuplicate(whitelistItem.value) == -1 && suggestionsCount--) list.push(whitelistItem); | ||
if (suggestionsCount == 0) break; | ||
@@ -838,0 +865,0 @@ } |
@@ -1,1 +0,1 @@ | ||
!function(t,e){"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?module.exports=e():t.Tagify=e()}(this,function(){"use strict";function t(t,e){if(!t)return console.warn("Tagify: ","invalid input element ",t),this;if(this.settings=this.extend({},this.DEFAULTS,e),this.settings.readonly=t.hasAttribute("readonly"),this.isIE&&(this.settings.autoComplete=!1),t.pattern)try{this.settings.pattern=new RegExp(t.pattern)}catch(t){}if(this.settings&&this.settings.delimiters)try{this.settings.delimiters=new RegExp("["+this.settings.delimiters+"]","g")}catch(t){}this.id=Math.random().toString(36).substr(2,9),this.value=[],this.listeners={},this.DOM={},this.extend(this,new this.EventDispatcher(this)),this.build(t),this.events.customBinding.call(this),this.events.binding.call(this)}return t.prototype={isIE:window.document.documentMode,DEFAULTS:{delimiters:",",pattern:null,maxTags:1/0,callbacks:{},addTagOnBlur:!0,duplicates:!1,whitelist:[],blacklist:[],enforceWhitelist:!1,autoComplete:!0,mapValueToProp:"",dropdown:{classname:"",enabled:2,maxItems:10}},customEventsList:["add","remove","duplicate","maxTagsExceed","blacklisted","notWhitelisted"],parseHTML:function(t){return(new DOMParser).parseFromString(t.trim(),"text/html").body.firstElementChild},escapeHtml:function(t){var e=document.createTextNode(t),i=document.createElement("p");return i.appendChild(e),i.innerHTML},build:function(t){var e=t.value,i='\n <tags class="tagify '+t.className+" "+(this.settings.readonly?"readonly":"")+'">\n <div contenteditable data-placeholder="'+t.placeholder+'" class="tagify--input"></div>\n </tags>';this.DOM.originalInput=t,this.DOM.scope=this.parseHTML(i),this.DOM.input=this.DOM.scope.querySelector("[contenteditable]"),t.parentNode.insertBefore(this.DOM.scope,t),this.settings.dropdown.enabled&&this.settings.whitelist.length&&this.dropdown.init.call(this),e&&this.addTags(e).forEach(function(t){t&&t.classList.add("tagify--noAnim")})},destroy:function(){this.DOM.scope.parentNode.removeChild(this.DOM.scope)},extend:function(t,e,i){function n(t){var e=Object.prototype.toString.call(t).split(" ")[1].slice(0,-1);return t===Object(t)&&"Array"!=e&&"Function"!=e&&"RegExp"!=e}function s(t,e){for(var i in e)e.hasOwnProperty(i)&&(n(e[i])?n(t[i])?s(t[i],e[i]):t[i]=Object.assign({},e[i]):t[i]=e[i])}return t instanceof Object||(t={}),s(t,e),i&&s(t,i),t},EventDispatcher:function(t){var e=document.createTextNode("");this.off=function(t,i){return i&&e.removeEventListener.call(e,t,i),this},this.on=function(t,i){return i&&e.addEventListener.call(e,t,i),this},this.trigger=function(i,n){var s;if(i)if(t.settings.isJQueryPlugin)$(t.DOM.originalInput).triggerHandler(i,[n]);else{try{s=new CustomEvent(i,{detail:n})}catch(t){console.warn(t)}e.dispatchEvent(s)}}},events:{customBinding:function(){var t=this;this.customEventsList.forEach(function(e){t.on(e,t.settings.callbacks[e])})},binding:function(){var t=!(arguments.length>0&&void 0!==arguments[0])||arguments[0],e=this.events.callbacks,i=this.listeners.main=this.listeners.main||{paste:["input",e.onPaste.bind(this)],focus:["input",e.onFocusBlur.bind(this)],blur:["input",e.onFocusBlur.bind(this)],keydown:["input",e.onKeydown.bind(this)],click:["scope",e.onClickScope.bind(this)]},n=t?"addEventListener":"removeEventListener";for(var s in i)this.DOM[i[s][0]][n](s,i[s][1]);t&&(this.DOM.input.addEventListener(this.isIE?"keydown":"input",e[this.isIE?"onInputIE":"onInput"].bind(this)),this.settings.isJQueryPlugin&&$(this.DOM.originalInput).on("tagify.removeAllTags",this.removeAllTags.bind(this)))},callbacks:{onFocusBlur:function(t){var e=t.target.textContent.trim();"focus"==t.type||("blur"==t.type&&e?this.settings.addTagOnBlur&&this.addTags(e,!0).length:(this.DOM.input.removeAttribute("style"),this.dropdown.hide.call(this)))},onKeydown:function(t){var e,i=t.target.textContent;"Backspace"!=t.key||""!=i&&8203!=i.charCodeAt(0)?"Escape"==t.key?(this.input.set.call(this),t.target.blur()):"Enter"==t.key?(t.preventDefault(),this.addTags(this.input.value,!0)):"ArrowRight"==t.key&&this.input.autocomplete.set.call(this):(e=(e=this.DOM.scope.querySelectorAll("tag:not(.tagify--hide)"))[e.length-1],this.removeTag(e))},onInput:function(t){var e=t.target.textContent.trim(),i=e.length>=this.settings.dropdown.enabled;this.input.value!=e&&(this.input.value=e,this.input.normalize.call(this),this.input.autocomplete.suggest.call(this,""),-1!=e.search(this.settings.delimiters)?this.addTags(e).length&&this.input.set.call(this):this.settings.dropdown.enabled&&this.settings.whitelist.length&&this.dropdown[i?"show":"hide"].call(this,e))},onInputIE:function(t){var e=this;setTimeout(function(){e.events.callbacks.onInput.call(e,t)})},onPaste:function(t){},onClickScope:function(t){"TAGS"==t.target.tagName?this.DOM.input.focus():"X"==t.target.tagName&&this.removeTag(t.target.parentNode)}}},input:{value:"",set:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"";this.input.value=this.DOM.input.innerHTML=t,t.length<2&&this.input.autocomplete.suggest.call(this,"")},normalize:function(){for(;this.DOM.input.firstElementChild;)this.DOM.input.removeChild(this.DOM.input.firstElementChild)},autocomplete:{suggest:function(t){t?this.DOM.input.setAttribute("data-suggest",t.substring(this.input.value.length)):this.DOM.input.removeAttribute("data-suggest")},set:function(){var t=this.DOM.input.getAttribute("data-suggest");t&&this.addTags(this.input.value+t).length&&(this.input.set.call(this),this.dropdown.hide.call(this))}}},getNodeIndex:function(t){for(var e=0;t=t.previousSibling;)3==t.nodeType&&/^\s*$/.test(t.data)||e++;return e},isTagDuplicate:function(t){return this.value.some(function(e){return t.toLowerCase()===e.value.toLowerCase()})},markTagByValue:function(t,e){var i,n;if(!e)for(n=(i=this.DOM.scope.querySelectorAll("tag")).length;n--;)i[n].value.toLowerCase().includes(t.toLowerCase())&&(e=i[n]);return!!e&&(e.classList.add("tagify--mark"),setTimeout(function(){e.classList.remove("tagify--mark")},2e3),!0)},isTagBlacklisted:function(t){return t=t.split(" "),this.settings.blacklist.filter(function(e){return-1!=t.indexOf(e)}).length},isTagWhitelisted:function(t){return this.settings.whitelist.some(function(e){if((e.value?e.value:e).toLowerCase()===t.toLowerCase())return!0})},addTags:function(t,e){function i(t){var e,i=t.value.trim(),n=this.value.length>=this.settings.maxTags;return i?this.settings.pattern&&!this.settings.pattern.test(i)?"pattern":this.isTagDuplicate(i)&&(this.trigger("duplicate",i),!this.settings.duplicates)?"duplicate":!!(!this.isTagBlacklisted(i)&&(!this.settings.enforceWhitelist||this.isTagWhitelisted(i))&&!n)||(t.class=t.class?t.class+" tagify--notAllowed":"tagify--notAllowed",n?e="maxTagsExceed":this.isTagBlacklisted(i)?e="blacklisted":this.settings.enforceWhitelist&&!this.isTagWhitelisted(i)&&(e="notWhitelisted"),this.trigger(e,{value:i,index:this.value.length}),"notAllowed"):"empty"}function n(t){this.DOM.scope.insertBefore(t,this.DOM.input)}var s=this,o=[];return this.DOM.input.removeAttribute("style"),(t=function(t){var e=this.settings.whitelist[0]instanceof Object,i=t instanceof Array&&"value"in t[0],n=t;if(i)return n;if(!i&&"string"==typeof t&&e){var s=this.settings.whitelist.filter(function(e){return e.value.toLowerCase()==t.toLowerCase()});s[0]&&(i=!0,n=s)}if(!i){if(!(t=t.trim()))return[];n=t.split(this.settings.delimiters).map(function(t){return{value:t.trim()}})}return n.filter(function(t){return t})}.call(this,t)).forEach(function(t){var e=i.call(s,t);if(!0===e||"notAllowed"==e){var a=s.createTagElem(t);n.call(s,a),"notAllowed"==e?setTimeout(function(){s.removeTag(a,!0)},1e3):(s.value.push(t),s.update(),s.trigger("add",s.extend({},{index:s.value.length,tag:a},t)),o.push(a))}}),t.length&&e&&this.input.set.call(this),o},createTagElem:function(t){var e,i=this.escapeHtml(t.value),n="<tag>\n <x></x><div><span title='"+i+"'>"+i+"</span></div>\n </tag>";return e=this.parseHTML(n),function(t,e){var i,n=Object.keys(e);for(i=n.length;i--;){var s=n[i];if(!e.hasOwnProperty(s))return;t.setAttribute(s,e[s])}}(e,t),e},removeTag:function(t,e){var i,n=this.getNodeIndex(t);t&&(t.style.width=parseFloat(window.getComputedStyle(t).width)+"px",document.body.clientTop,t.classList.add("tagify--hide"),setTimeout(function(){t.parentNode.removeChild(t)},400),e||(i=this.value.splice(n,1)[0],this.update(),this.trigger("remove",this.extend({},{index:n,tag:t},i))))},removeAllTags:function(){this.value=[],this.update(),Array.prototype.slice.call(this.DOM.scope.querySelectorAll("tag")).forEach(function(t){return t.parentNode.removeChild(t)})},update:function(){var t=this,e=this.value.map(function(e){return e[t.settings.mapValueToProp||"value"]||e.value});this.DOM.originalInput.value=JSON.stringify(e).slice(1,-1)},dropdown:{init:function(){this.DOM.dropdown=this.dropdown.build.call(this)},build:function(){var t='<div class="'+("tagify__dropdown "+this.settings.dropdown.classname).trim()+'"></div>';return this.parseHTML(t)},show:function(t){var e=this.dropdown.filterListItems.call(this,t),i=this.dropdown.createListHTML(e);e.length&&this.settings.autoComplete&&this.input.autocomplete.suggest.call(this,e[0].value),!i||e.length<2?this.dropdown.hide.call(this):(this.DOM.dropdown.innerHTML=i,this.dropdown.position.call(this),!this.DOM.dropdown.parentNode!=document.body&&(document.body.appendChild(this.DOM.dropdown),this.events.binding.call(this,!1),this.dropdown.events.binding.call(this)))},hide:function(){this.DOM.dropdown&&this.DOM.dropdown.parentNode==document.body&&(document.body.removeChild(this.DOM.dropdown),window.removeEventListener("resize",this.dropdown.position),this.dropdown.events.binding.call(this,!1),this.events.binding.call(this))},position:function(){var t=this.DOM.scope.getBoundingClientRect();this.DOM.dropdown.style.cssText="left: "+t.left+window.pageXOffset+"px; top: "+(t.top+t.height-1+window.pageYOffset)+"px; width: "+t.width+"px"},events:{binding:function(){var t=!(arguments.length>0&&void 0!==arguments[0])||arguments[0],e=this.listeners.dropdown=this.listeners.dropdown||{position:this.dropdown.position.bind(this),onKeyDown:this.dropdown.events.callbacks.onKeyDown.bind(this),onMouseOver:this.dropdown.events.callbacks.onMouseOver.bind(this),onClick:this.dropdown.events.callbacks.onClick.bind(this)},i=t?"addEventListener":"removeEventListener";window[i]("resize",e.position),window[i]("keydown",e.onKeyDown),window[i]("click",e.onClick),this.DOM.dropdown[i]("mouseover",e.onMouseOver)},callbacks:{onKeyDown:function(t){var e=this.DOM.dropdown.querySelectorAll("[class$='--active']")[0],i="";switch(t.key){case"ArrowDown":case"ArrowUp":case"Down":case"Up":t.preventDefault(),e=e?e["ArrowUp"==t.key||"Up"==t.key?"previousElementSibling":"nextElementSibling"]:this.DOM.dropdown.children["ArrowUp"==t.key||"Up"==t.key?this.DOM.dropdown.children.length-1:0],this.dropdown.highlightOption.call(this,e);break;case"Escape":this.dropdown.hide.call(this);break;case"Enter":t.preventDefault(),i=e?e.textContent:this.input.value,this.addTags(i,!0),this.dropdown.hide.call(this);break;case"ArrowRight":this.input.autocomplete.set.call(this)}},onMouseOver:function(t){t.target.className.includes("__item")&&this.dropdown.highlightOption.call(this,t.target)},onClick:function(t){t.target.className.includes("tagify__dropdown__item")&&(this.input.set.call(this),this.addTags(t.target.textContent)),this.dropdown.hide.call(this)}}},highlightOption:function(t){if(t){var e="tagify__dropdown__item--active";[].forEach.call(this.DOM.dropdown.querySelectorAll("[class$='--active']"),function(t){t.classList.remove(e)}),t.classList.add(e)}},filterListItems:function(t){if(!t)return"";for(var e,i=[],n=this.settings.whitelist,s=this.settings.dropdown.maxItems||1/0,o=0;o<n.length&&(e=n[o]instanceof Object?n[o]:{value:n[o]},0==e.value.toLowerCase().replace(/\s/g,"").indexOf(t.toLowerCase().replace(/\s/g,""))&&!this.isTagDuplicate(e.value)&&s--&&i.push(e),0!=s);o++);return i},createListHTML:function(t){function e(t){var e,i=Object.keys(t),n="";for(e=i.length;e--;){var s=i[e];if("class"!=s&&!t.hasOwnProperty(s))return;n+=" "+s+(t[s]?"="+t[s]:"")}return n}return t.map(function(t){return"<div class='tagify__dropdown__item "+(t.class?t.class:"")+"' "+e(t)+">"+t.value+"</div>"}).join("")}}},t}); | ||
!function(t,e){"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?module.exports=e():t.Tagify=e()}(this,function(){"use strict";function t(t,e){if(!t)return console.warn("Tagify: ","invalid input element ",t),this;if(this.settings=this.extend({},this.DEFAULTS,e),this.settings.readonly=t.hasAttribute("readonly"),this.isIE&&(this.settings.autoComplete=!1),t.pattern)try{this.settings.pattern=new RegExp(t.pattern)}catch(t){}if(this.settings&&this.settings.delimiters)try{this.settings.delimiters=new RegExp("["+this.settings.delimiters+"]","g")}catch(t){}this.id=Math.random().toString(36).substr(2,9),this.value=[],this.stringValue="",this.listeners={},this.DOM={},this.extend(this,new this.EventDispatcher(this)),this.build(t),this.events.customBinding.call(this),this.events.binding.call(this)}return t.prototype={isIE:window.document.documentMode,TEXTS:{empty:"empty",exceed:"number of tags exceeded",pattern:"pattern mismatch",duplicate:"already exists",notAllowed:"not allowed"},DEFAULTS:{delimiters:",",pattern:null,maxTags:1/0,callbacks:{},addTagOnBlur:!0,duplicates:!1,whitelist:[],blacklist:[],enforceWhitelist:!1,keepInvalidTags:!1,autoComplete:!0,mapValueToProp:"",dropdown:{classname:"",enabled:2,maxItems:10}},customEventsList:["add","remove","invalid"],parseHTML:function(t){return(new DOMParser).parseFromString(t.trim(),"text/html").body.firstElementChild},escapeHtml:function(t){var e=document.createTextNode(t),i=document.createElement("p");return i.appendChild(e),i.innerHTML},build:function(t){var e=t.value,i='\n <tags class="tagify '+t.className+" "+(this.settings.readonly?"readonly":"")+'">\n <div contenteditable data-placeholder="'+t.placeholder+'" class="tagify__input"></div>\n </tags>';this.DOM.originalInput=t,this.DOM.scope=this.parseHTML(i),this.DOM.input=this.DOM.scope.querySelector("[contenteditable]"),t.parentNode.insertBefore(this.DOM.scope,t),this.settings.dropdown.enabled&&this.settings.whitelist.length&&this.dropdown.init.call(this),e&&this.addTags(e).forEach(function(t){t&&t.classList.add("tagify--noAnim")}),t.autofocus&&this.DOM.input.focus()},destroy:function(){this.DOM.scope.parentNode.removeChild(this.DOM.scope)},extend:function(t,e,i){function n(t){var e=Object.prototype.toString.call(t).split(" ")[1].slice(0,-1);return t===Object(t)&&"Array"!=e&&"Function"!=e&&"RegExp"!=e}function s(t,e){for(var i in e)e.hasOwnProperty(i)&&(n(e[i])?n(t[i])?s(t[i],e[i]):t[i]=Object.assign({},e[i]):t[i]=e[i])}return t instanceof Object||(t={}),s(t,e),i&&s(t,i),t},EventDispatcher:function(t){var e=document.createTextNode("");this.off=function(t,i){return i&&e.removeEventListener.call(e,t,i),this},this.on=function(t,i){return i&&e.addEventListener.call(e,t,i),this},this.trigger=function(i,n){var s;if(i)if(t.settings.isJQueryPlugin)$(t.DOM.originalInput).triggerHandler(i,[n]);else{try{s=new CustomEvent(i,{detail:n})}catch(t){console.warn(t)}e.dispatchEvent(s)}}},events:{customBinding:function(){var t=this;this.customEventsList.forEach(function(e){t.on(e,t.settings.callbacks[e])})},binding:function(){var t=!(arguments.length>0&&void 0!==arguments[0])||arguments[0],e=this.events.callbacks,i=this.listeners.main=this.listeners.main||{paste:["input",e.onPaste.bind(this)],focus:["input",e.onFocusBlur.bind(this)],blur:["input",e.onFocusBlur.bind(this)],keydown:["input",e.onKeydown.bind(this)],click:["scope",e.onClickScope.bind(this)]},n=t?"addEventListener":"removeEventListener";for(var s in i)this.DOM[i[s][0]][n](s,i[s][1]);t&&(this.DOM.input.addEventListener(this.isIE?"keydown":"input",e[this.isIE?"onInputIE":"onInput"].bind(this)),this.settings.isJQueryPlugin&&$(this.DOM.originalInput).on("tagify.removeAllTags",this.removeAllTags.bind(this)))},callbacks:{onFocusBlur:function(t){var e=t.target.textContent.trim();"focus"==t.type||("blur"==t.type&&e?this.settings.addTagOnBlur&&this.addTags(e,!0).length:(this.DOM.input.removeAttribute("style"),this.dropdown.hide.call(this)))},onKeydown:function(t){var e,i=t.target.textContent;"Backspace"!=t.key||""!=i&&8203!=i.charCodeAt(0)?"Escape"==t.key?(this.input.set.call(this),t.target.blur()):"Enter"==t.key?(t.preventDefault(),this.addTags(this.input.value,!0)):"ArrowRight"==t.key&&this.input.autocomplete.set.call(this):(e=(e=this.DOM.scope.querySelectorAll("tag:not(.tagify--hide)"))[e.length-1],this.removeTag(e))},onInput:function(t){var e=this.input.normalize.call(this),i=e.length>=this.settings.dropdown.enabled;e?this.input.value!=e&&(this.input.set.call(this,e,!1),-1!=e.search(this.settings.delimiters)?this.addTags(e).length&&this.input.set.call(this):this.settings.dropdown.enabled&&this.settings.whitelist.length&&this.dropdown[i?"show":"hide"].call(this,e)):this.input.set.call(this,"")},onInputIE:function(t){var e=this;setTimeout(function(){e.events.callbacks.onInput.call(e,t)})},onPaste:function(t){},onClickScope:function(t){"TAGS"==t.target.tagName?this.DOM.input.focus():"X"==t.target.tagName&&this.removeTag(t.target.parentNode)}}},input:{value:"",set:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",e=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];this.input.value=t,e&&(this.DOM.input.innerHTML=t),t.length<2&&this.input.autocomplete.suggest.call(this,""),this.input.validate.call(this)},validate:function(){var t=!this.input.value||this.validateTag.call(this,this.input.value);this.DOM.input.classList.toggle("tagify__input--invalid",!0!==t)},normalize:function(){for(var t=this.DOM.input.cloneNode(!0),e=t.textContent.replace(/\s/g," ");t.firstElementChild;)e+=t.firstElementChild.textContent,t.removeChild(t.firstElementChild);return e.replace(/^\s+/,"")},autocomplete:{suggest:function(t){t?this.DOM.input.setAttribute("data-suggest",t.substring(this.input.value.length)):this.DOM.input.removeAttribute("data-suggest")},set:function(){var t=this.DOM.input.getAttribute("data-suggest");t&&this.addTags(this.input.value+t).length&&(this.input.set.call(this),this.dropdown.hide.call(this))}}},getNodeIndex:function(t){for(var e=0;t=t.previousSibling;)3==t.nodeType&&/^\s*$/.test(t.data)||e++;return e},isTagDuplicate:function(t){return this.value.findIndex(function(e){return t.toLowerCase()===e.value.toLowerCase()})},markTagByValue:function(t,e){var i;return e||(i=this.isTagDuplicate.call(this,t),e=this.DOM.scope.querySelectorAll("tag")[i]),!!e&&(e.classList.add("tagify--mark"),setTimeout(function(){e.classList.remove("tagify--mark")},100),e)},isTagBlacklisted:function(t){return t=t.split(" "),this.settings.blacklist.filter(function(e){return-1!=t.indexOf(e)}).length},isTagWhitelisted:function(t){return this.settings.whitelist.some(function(e){if((e.value?e.value:e).toLowerCase()===t.toLowerCase())return!0})},validateTag:function(t){var e=t.trim(),i=this.value.length>=this.settings.maxTags,n=!0;return e?i?n=this.TEXTS.exceed:this.settings.pattern&&!this.settings.pattern.test(e)?n=this.TEXTS.pattern:this.settings.duplicates||-1===this.isTagDuplicate(e)?(this.isTagBlacklisted(e)||this.settings.enforceWhitelist&&!this.isTagWhitelisted(e))&&(n=this.TEXTS.notAllowed):n=this.TEXTS.duplicate:n=this.TEXTS.empty,n},addTags:function(t,e){function i(t){this.DOM.scope.insertBefore(t,this.DOM.input)}var n=this,s=[];return this.DOM.input.removeAttribute("style"),(t=function(t){var e=this.settings.whitelist[0]instanceof Object,i=t instanceof Array&&"value"in t[0],n=t;if(i)return n;if(!i&&"string"==typeof t&&e){var s=this.settings.whitelist.filter(function(e){return e.value.toLowerCase()==t.toLowerCase()});s[0]&&(i=!0,n=s)}if(!i){if(!t.trim())return[];n=t.split(this.settings.delimiters).filter(function(t){return t}).map(function(t){return{value:t.trim()}})}return n}.call(this,t)).forEach(function(t){var e,a=n.validateTag.call(n,t.value);!0!==a&&(t.class=t.class?t.class+" tagify--notAllowed":"tagify--notAllowed",t.title=a,n.markTagByValue.call(n,t.value),n.trigger("invalid",{value:t.value,index:n.value.length,message:a})),e=n.createTagElem(t),s.push(e),i.call(n,e),!0===a?(n.value.push(t),n.update(),n.trigger("add",n.extend({},{index:n.value.length,tag:e},t))):n.settings.keepInvalidTags||setTimeout(function(){n.removeTag(e,!0)},1e3)}),t.length&&e&&this.input.set.call(this),s},createTagElem:function(t){var e,i=this.escapeHtml(t.value),n="<tag title='"+i+"'>\n <x title=''></x><div><span>"+i+"</span></div>\n </tag>";return e=this.parseHTML(n),function(t,e){var i,n=Object.keys(e);for(i=n.length;i--;){var s=n[i];if(!e.hasOwnProperty(s))return;t.setAttribute(s,e[s])}}(e,t),e},removeTag:function(t,e){if(t){var i,n=this.getNodeIndex(t);t&&(t.style.width=parseFloat(window.getComputedStyle(t).width)+"px",document.body.clientTop,t.classList.add("tagify--hide"),setTimeout(function(){t.parentNode.removeChild(t)},400),e||(i=this.value.splice(n,1)[0],this.update(),this.trigger("remove",this.extend({},{index:n,tag:t},i))))}},removeAllTags:function(){this.value=[],this.update(),Array.prototype.slice.call(this.DOM.scope.querySelectorAll("tag")).forEach(function(t){return t.parentNode.removeChild(t)})},update:function(){var t=this,e=this.value.map(function(e){return e[t.settings.mapValueToProp||"value"]||e.value});this.stringValue=JSON.stringify(e).slice(1,-1),this.DOM.originalInput.value=this.stringValue},dropdown:{init:function(){this.DOM.dropdown=this.dropdown.build.call(this)},build:function(){var t='<div class="'+("tagify__dropdown "+this.settings.dropdown.classname).trim()+'"></div>';return this.parseHTML(t)},show:function(t){var e=this.dropdown.filterListItems.call(this,t),i=this.dropdown.createListHTML(e);this.settings.autoComplete&&this.input.autocomplete.suggest.call(this,e.length?e[0].value:""),!i||e.length<2?this.dropdown.hide.call(this):(this.DOM.dropdown.innerHTML=i,this.dropdown.position.call(this),!this.DOM.dropdown.parentNode!=document.body&&(document.body.appendChild(this.DOM.dropdown),this.events.binding.call(this,!1),this.dropdown.events.binding.call(this)))},hide:function(){this.DOM.dropdown&&this.DOM.dropdown.parentNode==document.body&&(document.body.removeChild(this.DOM.dropdown),window.removeEventListener("resize",this.dropdown.position),this.dropdown.events.binding.call(this,!1),this.events.binding.call(this))},position:function(){var t=this.DOM.scope.getBoundingClientRect();this.DOM.dropdown.style.cssText="left: "+(t.left+window.pageXOffset)+"px; top: "+(t.top+t.height-1+window.pageYOffset)+"px; width: "+t.width+"px"},events:{binding:function(){var t=!(arguments.length>0&&void 0!==arguments[0])||arguments[0],e=this.listeners.dropdown=this.listeners.dropdown||{position:this.dropdown.position.bind(this),onKeyDown:this.dropdown.events.callbacks.onKeyDown.bind(this),onMouseOver:this.dropdown.events.callbacks.onMouseOver.bind(this),onClick:this.dropdown.events.callbacks.onClick.bind(this)},i=t?"addEventListener":"removeEventListener";window[i]("resize",e.position),window[i]("keydown",e.onKeyDown),window[i]("click",e.onClick),this.DOM.dropdown[i]("mouseover",e.onMouseOver)},callbacks:{onKeyDown:function(t){var e=this.DOM.dropdown.querySelectorAll("[class$='--active']")[0],i="";switch(t.key){case"ArrowDown":case"ArrowUp":case"Down":case"Up":t.preventDefault(),e=e?e["ArrowUp"==t.key||"Up"==t.key?"previousElementSibling":"nextElementSibling"]:this.DOM.dropdown.children["ArrowUp"==t.key||"Up"==t.key?this.DOM.dropdown.children.length-1:0],this.dropdown.highlightOption.call(this,e);break;case"Escape":this.dropdown.hide.call(this);break;case"Enter":t.preventDefault(),i=e?e.textContent:this.input.value,this.addTags(i,!0),this.dropdown.hide.call(this);break;case"ArrowRight":this.input.autocomplete.set.call(this)}},onMouseOver:function(t){t.target.className.includes("__item")&&this.dropdown.highlightOption.call(this,t.target)},onClick:function(t){t.target.className.includes("tagify__dropdown__item")&&(this.input.set.call(this),this.addTags(t.target.textContent)),this.dropdown.hide.call(this)}}},highlightOption:function(t){if(t){var e="tagify__dropdown__item--active";[].forEach.call(this.DOM.dropdown.querySelectorAll("[class$='--active']"),function(t){t.classList.remove(e)}),t.classList.add(e)}},filterListItems:function(t){if(!t)return"";for(var e,i=[],n=this.settings.whitelist,s=this.settings.dropdown.maxItems||1/0,a=0;a<n.length&&(e=n[a]instanceof Object?n[a]:{value:n[a]},0==e.value.toLowerCase().replace(/\s/g,"").indexOf(t.toLowerCase().replace(/\s/g,""))&&-1==this.isTagDuplicate(e.value)&&s--&&i.push(e),0!=s);a++);return i},createListHTML:function(t){function e(t){var e,i=Object.keys(t),n="";for(e=i.length;e--;){var s=i[e];if("class"!=s&&!t.hasOwnProperty(s))return;n+=" "+s+(t[s]?"="+t[s]:"")}return n}return t.map(function(t){return"<div class='tagify__dropdown__item "+(t.class?t.class:"")+"' "+e(t)+">"+t.value+"</div>"}).join("")}}},t}); |
(function(){ | ||
if( !String.prototype.includes ){ | ||
String.prototype.includes = function(search, start) { | ||
'use strict'; | ||
if (typeof start !== 'number') | ||
start = 0; | ||
if( !String.prototype.includes ){ | ||
String.prototype.includes = function(search, start) { | ||
'use strict'; | ||
if (typeof start !== 'number') | ||
start = 0; | ||
if (start + search.length > this.length) | ||
return false; | ||
if (start + search.length > this.length) | ||
return false; | ||
else | ||
return this.indexOf(search, start) !== -1; | ||
}; | ||
} | ||
else | ||
return this.indexOf(search, start) !== -1; | ||
}; | ||
} | ||
////////////////////////////////////////////////////////////////// | ||
// https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill | ||
if ( typeof window.CustomEvent === "function" ) return false; | ||
////////////////////////////////////////////////////////////////// | ||
// https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill | ||
if ( typeof window.CustomEvent === "function" ) return false; | ||
function CustomEvent ( event, params ) { | ||
params = params || { bubbles: false, cancelable: false, detail: undefined }; | ||
var evt = document.createEvent( 'CustomEvent' ); | ||
evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail ); | ||
return evt; | ||
} | ||
function CustomEvent ( event, params ) { | ||
params = params || { bubbles: false, cancelable: false, detail: undefined }; | ||
var evt = document.createEvent( 'CustomEvent' ); | ||
evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail ); | ||
return evt; | ||
} | ||
CustomEvent.prototype = window.Event.prototype; | ||
CustomEvent.prototype = window.Event.prototype; | ||
window.CustomEvent = CustomEvent; | ||
window.CustomEvent = CustomEvent; | ||
////////////////////////////////////////////////////////////////// | ||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill | ||
// | ||
if (typeof Object.assign != 'function') { | ||
// Must be writable: true, enumerable: false, configurable: true | ||
Object.defineProperty(Object, "assign", { | ||
value: function assign(target, varArgs) { // .length of function is 2 | ||
'use strict'; | ||
if (target == null) { // TypeError if undefined or null | ||
throw new TypeError('Cannot convert undefined or null to object'); | ||
} | ||
////////////////////////////////////////////////////////////////// | ||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill | ||
// | ||
if (typeof Object.assign != 'function') { | ||
// Must be writable: true, enumerable: false, configurable: true | ||
Object.defineProperty(Object, "assign", { | ||
value: function assign(target, varArgs) { // .length of function is 2 | ||
'use strict'; | ||
if (target == null) { // TypeError if undefined or null | ||
throw new TypeError('Cannot convert undefined or null to object'); | ||
} | ||
var to = Object(target); | ||
var to = Object(target); | ||
for (var index = 1; index < arguments.length; index++) { | ||
var nextSource = arguments[index]; | ||
for (var index = 1; index < arguments.length; index++) { | ||
var nextSource = arguments[index]; | ||
if (nextSource != null) { // Skip over if undefined or null | ||
for (var nextKey in nextSource) { | ||
// Avoid bugs when hasOwnProperty is shadowed | ||
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { | ||
to[nextKey] = nextSource[nextKey]; | ||
if (nextSource != null) { // Skip over if undefined or null | ||
for (var nextKey in nextSource) { | ||
// Avoid bugs when hasOwnProperty is shadowed | ||
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { | ||
to[nextKey] = nextSource[nextKey]; | ||
} | ||
} | ||
} | ||
} | ||
return to; | ||
}, | ||
writable: true, | ||
configurable: true | ||
}); | ||
} | ||
if (!Array.prototype.findIndex) { | ||
Object.defineProperty(Array.prototype, 'findIndex', { | ||
value: function(predicate) { | ||
if (this == null) | ||
throw new TypeError('"this" is null or not defined'); | ||
var o = Object(this), len = o.length >>> 0; | ||
if (typeof predicate !== 'function') { | ||
throw new TypeError('predicate must be a function'); | ||
} | ||
} | ||
} | ||
} | ||
return to; | ||
}, | ||
writable: true, | ||
configurable: true | ||
}); | ||
var thisArg = arguments[1], k = 0; | ||
while (k < len) { | ||
var kValue = o[k]; | ||
if (predicate.call(thisArg, kValue, k, o)) { | ||
return k; | ||
} | ||
k++; | ||
} | ||
return -1; | ||
}, | ||
configurable: true, | ||
writable: true | ||
}); | ||
} | ||
})() | ||
{ | ||
"name": "@yaireo/tagify", | ||
"version": "2.0.3", | ||
"version": "2.1.0", | ||
"homepage": "https://github.com/yairEO/tagify", | ||
@@ -5,0 +5,0 @@ "description": "Want to simply convert an input field into a tags element, in a easy customizable way, with good performance and smallest code footprint? You are in the right place my friend.", |
@@ -1,2 +0,2 @@ | ||
Tagify - lightweight input "tags" script | ||
[Tagify](https://yaireo.github.io/tagify) - lightweight input "tags" script | ||
======== | ||
@@ -24,3 +24,3 @@ | ||
## Selling points | ||
* JS weights `~12kb` (`4kb` GZIP) (less than 900 easily understandale lines of code) | ||
* JS at `~12kb` (`4kb` GZIP) (less than *900* easily understandable lines of code) | ||
* SCSS file is `~6kb` of well-crafted flexible code | ||
@@ -30,3 +30,3 @@ * Easily change direction to RTL via the SCSS file only | ||
* Easily customized | ||
* Exposed custom events (Add, Remove, Invalid, Duplicate) | ||
* Exposed custom events (add, remove, invalid) | ||
* For Internet Explorer 11 include the script `tagify.polyfills.js` under `/dist` | ||
@@ -84,2 +84,3 @@ | ||
.on('add', onTagAdded); | ||
.on('invalid', onInvaildTag) | ||
@@ -95,2 +96,6 @@ function onTagRemoved(e){ | ||
} | ||
function onInvaildTag(e){ | ||
// e.detail ... | ||
} | ||
``` | ||
@@ -234,2 +239,3 @@ | ||
## Exposed events | ||
@@ -241,9 +247,5 @@ | ||
remove | A tag has been removed | ||
duplicate | A tag has been added and found to be a duplicate of existing one | ||
maxTagsExceed | Number of tags exceeds the allowed quantity and the exceed tags were denied (removed) | ||
blacklisted | A tag which is in the blacklist has been added and denied (removed) | ||
notWhitelisted | A tag which is not in the whitelist has been added and denied (removed) | ||
invalid | A tag has been added but did not pass vaildation. See [event detail](https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events) | ||
## Settings | ||
@@ -266,4 +268,2 @@ | ||
dropdown.maxItems | Number | 10 | maximum items to show in the suggestions list dropdown | ||
dropdown.classname | String | "" | custom class name for the dropdown suggestions selectbox | ||
dropdown.classname | String | "" | custom class name for the dropdown suggestions selectbox |
@@ -26,2 +26,3 @@ function Tagify( input, settings ){ | ||
this.value = []; // An array holding all the (currently used) tags | ||
this.stringValue = ""; // same as above, only as a String | ||
@@ -42,13 +43,22 @@ // events' callbacks references will be stores here, so events could be unbinded | ||
TEXTS : { | ||
empty : "empty", | ||
exceed : "number of tags exceeded", | ||
pattern : "pattern mismatch", | ||
duplicate : "already exists", | ||
notAllowed : "not allowed" | ||
}, | ||
DEFAULTS : { | ||
delimiters : ",", // [regex] split tags by any of these delimiters ("null" to cancel) Example: ",| |." | ||
pattern : null, // regex pattern to validate input by. Ex: /[1-9]/ | ||
maxTags : Infinity, // maximum number of tags | ||
callbacks : {}, // exposed callbacks object to be triggered on certain events | ||
addTagOnBlur : true, // flag - automatically adds the text which was inputed as a tag when blur event happens | ||
duplicates : false, // flag - allow tuplicate tags | ||
whitelist : [], // is this list has any items, then only allow tags from this list | ||
blacklist : [], // a list of non-allowed tags | ||
enforceWhitelist : false, // flag - should ONLY use tags allowed in whitelist | ||
autoComplete : true, // flag - tries to autocomplete the input's value while typing | ||
delimiters : ",", // [RegEx] split tags by any of these delimiters ("null" to cancel) Example: ",| |." | ||
pattern : null, // RegEx pattern to validate input by. Ex: /[1-9]/ | ||
maxTags : Infinity, // Maximum number of tags | ||
callbacks : {}, // Exposed callbacks object to be triggered on certain events | ||
addTagOnBlur : true, // Flag - automatically adds the text which was inputed as a tag when blur event happens | ||
duplicates : false, // Flag - allow tuplicate tags | ||
whitelist : [], // Array of tags to suggest as the user types (can be used along with "enforceWhitelist" setting) | ||
blacklist : [], // A list of non-allowed tags | ||
enforceWhitelist : false, // Flag - Only allow tags allowed in whitelist | ||
keepInvalidTags : false, // Flag - if true, do not remove tags which did not pass validation | ||
autoComplete : true, // Flag - tries to autocomplete the input's value while typing | ||
mapValueToProp : "", // String - when tags have multiple properties, and for each tag another property should be used besides the "value" | ||
@@ -62,3 +72,3 @@ dropdown : { | ||
customEventsList : ['add', 'remove', 'duplicate', 'maxTagsExceed', 'blacklisted', 'notWhitelisted'], | ||
customEventsList : ['add', 'remove', 'invalid'], | ||
@@ -95,3 +105,3 @@ /** | ||
<tags class="tagify ${input.className} ${this.settings.readonly ? 'readonly' : ''}"> | ||
<div contenteditable data-placeholder="${input.placeholder}" class="tagify--input"></div> | ||
<div contenteditable data-placeholder="${input.placeholder}" class="tagify__input"></div> | ||
</tags>`; | ||
@@ -104,3 +114,2 @@ | ||
// if "autocomplete" flag on toggeled & "whitelist" has items, build suggestions list | ||
if( this.settings.dropdown.enabled && this.settings.whitelist.length ){ | ||
@@ -115,2 +124,4 @@ this.dropdown.init.call(this); | ||
}); | ||
input.autofocus && this.DOM.input.focus() | ||
}, | ||
@@ -282,17 +293,23 @@ | ||
onInput(e){ | ||
var value = e.target.textContent.trim(), | ||
var value = this.input.normalize.call(this), | ||
showSuggestions = value.length >= this.settings.dropdown.enabled; | ||
if( this.input.value == value ) return; | ||
// save the value on the input state object | ||
this.input.value = value; | ||
this.input.normalize.call(this); | ||
this.input.autocomplete.suggest.call(this, ''); // cleanup any possible previous suggestion | ||
if( !value ){ | ||
this.input.set.call(this, ''); | ||
return; | ||
} | ||
if( this.input.value == value ) return; // for IE; since IE doesn't have an "input" event so "keyDown" is used instead | ||
// save the value on the input's State object | ||
this.input.set.call(this, value, false); | ||
if( value.search(this.settings.delimiters) != -1 ){ | ||
if( this.addTags( value ).length ) | ||
if( this.addTags( value ).length ){ | ||
this.input.set.call(this); // clear the input field's value | ||
} | ||
} | ||
else if( this.settings.dropdown.enabled && this.settings.whitelist.length ) | ||
else if( this.settings.dropdown.enabled && this.settings.whitelist.length ){ | ||
this.dropdown[showSuggestions ? "show" : "hide"].call(this, value); | ||
} | ||
}, | ||
@@ -327,14 +344,33 @@ | ||
value : '', | ||
set(s = ''){ | ||
this.input.value = this.DOM.input.innerHTML = s; | ||
set(s = '', updateDOM = true){ | ||
this.input.value = s; | ||
if( updateDOM ) | ||
this.DOM.input.innerHTML = s; | ||
if( s.length < 2 ) | ||
this.input.autocomplete.suggest.call(this, ''); | ||
this.input.validate.call(this); | ||
}, | ||
/** | ||
* Marks the tagify's input as "invalid" if the value did not pass "validateTag()" | ||
*/ | ||
validate(){ | ||
var isValid = !this.input.value || this.validateTag.call(this, this.input.value); | ||
this.DOM.input.classList.toggle('tagify__input--invalid', isValid !== true); | ||
}, | ||
// remove any child DOM elements that aren't of type TEXT (like <br>) | ||
normalize(){ | ||
while (this.DOM.input.firstElementChild ){ | ||
this.DOM.input.removeChild(this.DOM.input.firstElementChild ); | ||
var clone = this.DOM.input.cloneNode(true), | ||
v = clone.textContent.replace(/\s/g,' '); // replace NBSPs with spaces characters | ||
while( clone.firstElementChild ){ | ||
v += clone.firstElementChild.textContent; | ||
clone.removeChild(clone.firstElementChild); | ||
} | ||
return v.replace(/^\s+/,""); // trimLeft | ||
}, | ||
@@ -376,3 +412,4 @@ | ||
isTagDuplicate(s){ | ||
return this.value.some(item => s.toLowerCase() === item.value.toLowerCase()); | ||
return this.value.findIndex(item => s.toLowerCase() === item.value.toLowerCase()); | ||
// return this.value.some(item => s.toLowerCase() === item.value.toLowerCase()); | ||
}, | ||
@@ -387,10 +424,7 @@ | ||
markTagByValue(value, tagElm){ | ||
var tagsElms, tagsElmsLen; | ||
var tagsElms, tagsElmsLen, tagIdx | ||
if( !tagElm ){ | ||
tagsElms = this.DOM.scope.querySelectorAll('tag'); | ||
for( tagsElmsLen = tagsElms.length; tagsElmsLen--; ){ | ||
if( tagsElms[tagsElmsLen].value.toLowerCase().includes(value.toLowerCase()) ) | ||
tagElm = tagsElms[tagsElmsLen]; | ||
} | ||
tagIdx = this.isTagDuplicate.call(this, value); | ||
tagElm = this.DOM.scope.querySelectorAll('tag')[tagIdx]; | ||
} | ||
@@ -401,10 +435,6 @@ | ||
tagElm.classList.add('tagify--mark'); | ||
setTimeout(() => { tagElm.classList.remove('tagify--mark') }, 2000); | ||
return true; | ||
setTimeout(() => { tagElm.classList.remove('tagify--mark') }, 100); | ||
return tagElm; | ||
} | ||
else{ | ||
} | ||
return false; | ||
@@ -433,2 +463,35 @@ }, | ||
/** | ||
* validate a tag object BEFORE the actual tag will be created & appeneded | ||
* @param {String} s | ||
* @return {Boolean/String} ["true" if validation has passed, String for a fail] | ||
*/ | ||
validateTag( s ){ | ||
var value = s.trim(), | ||
maxTagsExceed = this.value.length >= this.settings.maxTags, | ||
isDuplicate, | ||
eventName__error, | ||
result = true; | ||
// check for empty value | ||
if( !value ) | ||
result = this.TEXTS.empty; | ||
else if( maxTagsExceed ) | ||
result = this.TEXTS.exceed; | ||
// check if pattern should be used and if so, use it to test the value | ||
else if( this.settings.pattern && !(this.settings.pattern.test(value)) ) | ||
result = this.TEXTS.pattern; | ||
// if duplicates are not allowed and there is a duplicate | ||
else if( !this.settings.duplicates && this.isTagDuplicate(value) !== -1 ) | ||
result = this.TEXTS.duplicate; | ||
else if( this.isTagBlacklisted(value) ||(this.settings.enforceWhitelist && !this.isTagWhitelisted(value)) ) | ||
result = this.TEXTS.notAllowed; | ||
return result; | ||
}, | ||
/** | ||
* add a "tag" element to the "tags" component | ||
@@ -440,4 +503,3 @@ * @param {String/Array} tagsItems [A string (single or multiple values with a delimiter), or an Array of Objects] | ||
addTags( tagsItems, clearInput ){ | ||
var that = this, | ||
tagElems = []; | ||
var tagElems = []; | ||
@@ -472,63 +534,12 @@ this.DOM.input.removeAttribute('style'); | ||
if( !isComplex ){ | ||
tagsItems = tagsItems.trim(); | ||
if( !tagsItems ) return []; | ||
if( !tagsItems.trim() ) return []; | ||
// go over each tag and add it (if there were multiple ones) | ||
result = tagsItems.split(this.settings.delimiters).map(v => ({ value:v.trim() })); | ||
result = tagsItems.split(this.settings.delimiters).filter(n => n).map(v => ({ value:v.trim() })); | ||
} | ||
return result.filter(n => n); // cleanup the array from "undefined", "false" or empty items; | ||
return result; | ||
} | ||
/** | ||
* validate a tag object BEFORE the actual tag will be created & appeneded | ||
* @param {Object} tagData [{"value":"text", "class":whatever", ...}] | ||
* @return {Boolean/String} ["true" if validation has passed, String or "false" for any type of error] | ||
*/ | ||
function validateTag( tagData ){ | ||
var value = tagData.value.trim(), | ||
maxTagsExceed = this.value.length >= this.settings.maxTags, | ||
isDuplicate, | ||
eventName__error, | ||
tagAllowed; | ||
// check for empty value | ||
if( !value ) | ||
return "empty"; | ||
// check if pattern should be used and if so, use it to test the value | ||
if( this.settings.pattern && !(this.settings.pattern.test(value)) ) | ||
return "pattern"; | ||
// check if the tag already exists | ||
if( this.isTagDuplicate(value) ){ | ||
this.trigger('duplicate', value); | ||
if( !this.settings.duplicates ){ | ||
// this.markTagByValue(value, tagElm) | ||
return "duplicate"; | ||
} | ||
} | ||
// check if the tag is allowed by the rules set | ||
tagAllowed = !this.isTagBlacklisted(value) && (!this.settings.enforceWhitelist || this.isTagWhitelisted(value)) && !maxTagsExceed; | ||
// Check against blacklist & whitelist (if enforced) | ||
if( !tagAllowed ){ | ||
tagData.class = tagData.class ? tagData.class + " tagify--notAllowed" : "tagify--notAllowed"; | ||
// broadcast why the tag was not allowed | ||
if( maxTagsExceed ) eventName__error = 'maxTagsExceed'; | ||
else if( this.isTagBlacklisted(value) ) eventName__error = 'blacklisted'; | ||
else if( this.settings.enforceWhitelist && !this.isTagWhitelisted(value) ) eventName__error = 'notWhitelisted'; | ||
this.trigger(eventName__error, {value:value, index:this.value.length}); | ||
return "notAllowed"; | ||
} | ||
return true; | ||
} | ||
/** | ||
* appened (validated) tag to the component's DOM scope | ||
@@ -545,25 +556,29 @@ * @return {[type]} [description] | ||
tagsItems.forEach(tagData => { | ||
var tagValidation = this.validateTag.call(this, tagData.value), | ||
tagElm; | ||
var isTagValidated = validateTag.call(that, tagData); | ||
if( isTagValidated === true || isTagValidated == "notAllowed" ){ | ||
// create the tag element | ||
var tagElm = that.createTagElem(tagData); | ||
if( tagValidation !== true ){ | ||
tagData.class = tagData.class ? tagData.class + " tagify--notAllowed" : "tagify--notAllowed"; | ||
tagData.title = tagValidation; | ||
this.markTagByValue.call(this, tagData.value); | ||
this.trigger("invalid", {value:tagData.value, index:this.value.length, message:tagValidation}); | ||
} | ||
// add the tag to the component's DOM | ||
appendTag.call(that, tagElm); | ||
// Create tag HTML element | ||
tagElm = this.createTagElem(tagData); | ||
tagElems.push(tagElm); | ||
// remove the tag "slowly" | ||
if( isTagValidated == "notAllowed" ){ | ||
setTimeout(() => { that.removeTag(tagElm, true) }, 1000); | ||
} | ||
// add the tag to the component's DOM | ||
appendTag.call(this, tagElm); | ||
else{ | ||
// update state | ||
that.value.push(tagData); | ||
that.update(); | ||
that.trigger('add', that.extend({}, {index:that.value.length, tag:tagElm}, tagData)); | ||
tagElems.push(tagElm); | ||
} | ||
if( tagValidation === true ){ | ||
// update state | ||
this.value.push(tagData); | ||
this.update(); | ||
this.trigger('add', this.extend({}, {index:this.value.length, tag:tagElm}, tagData)); | ||
} | ||
else if( !this.settings.keepInvalidTags ){ | ||
// remove invalid tags (if "keepInvalidTags" is set to "false") | ||
setTimeout(() => { this.removeTag(tagElm, true) }, 1000); | ||
} | ||
}) | ||
@@ -586,4 +601,4 @@ | ||
escapedValue = this.escapeHtml(tagData.value), | ||
template = `<tag> | ||
<x></x><div><span title='${escapedValue}'>${escapedValue}</span></div> | ||
template = `<tag title='${escapedValue}'> | ||
<x title=''></x><div><span>${escapedValue}</span></div> | ||
</tag>`; | ||
@@ -615,2 +630,4 @@ | ||
removeTag( tagElm, silent ){ | ||
if( !tagElm ) return; | ||
var tagData, | ||
@@ -649,3 +666,4 @@ tagIdx = this.getNodeIndex(tagElm); | ||
var tagsAsString = this.value.map(v => v[this.settings.mapValueToProp || "value"] || v.value); | ||
this.DOM.originalInput.value = JSON.stringify(tagsAsString).slice(1,-1); | ||
this.stringValue = JSON.stringify(tagsAsString).slice(1,-1); | ||
this.DOM.originalInput.value = this.stringValue; | ||
}, | ||
@@ -672,4 +690,5 @@ | ||
if( listItems.length && this.settings.autoComplete ) | ||
this.input.autocomplete.suggest.call(this, listItems[0].value); | ||
if( this.settings.autoComplete ){ | ||
this.input.autocomplete.suggest.call(this, listItems.length ? listItems[0].value : ''); | ||
} | ||
@@ -706,3 +725,3 @@ if( !listHTML || listItems.length < 2 ){ | ||
this.DOM.dropdown.style.cssText = "left: " + rect.left + window.pageXOffset + "px; \ | ||
this.DOM.dropdown.style.cssText = "left: " + (rect.left + window.pageXOffset) + "px; \ | ||
top: " + (rect.top + rect.height - 1 + window.pageYOffset) + "px; \ | ||
@@ -830,3 +849,3 @@ width: " + rect.width + "px"; | ||
// match for the value within each "whitelist" item | ||
if( valueIsInWhitelist && !this.isTagDuplicate(whitelistItem.value) && suggestionsCount-- ) | ||
if( valueIsInWhitelist && this.isTagDuplicate(whitelistItem.value) == -1 && suggestionsCount-- ) | ||
list.push(whitelistItem); | ||
@@ -833,0 +852,0 @@ if( suggestionsCount == 0 ) break; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
299260
2407
262