Tags input with flexbox & javascript
Created
December 30, 2021 14:45
-
-
Save neisdev/5a69d98c7e7e97b44f80f91f46ab4c69 to your computer and use it in GitHub Desktop.
Tags input
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<h3>Tags input with flexbox & javascript(IE9 inline-block)</h3> | |
<form action="" class="test" method="post"> | |
<label for="exist-values">Exist values | |
<input type="text" id="exist-values" class="tagged form-control" data-removeBtn="true" name="tag-2" value="PC,Play Station 4,X-BOX One" placeholder="Add Platform"> | |
</label> | |
<button type="submit" name="button">Send</button> | |
</form> | |
<button id="destroy">destroy</button> | |
<button id="add">add tags</button> | |
<button id="addArr">add tags array</button> | |
<button id="clear">clear tags</button> | |
<button id="get">get taggs</button> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://github.com/k-ivan/Tags | |
(function() { | |
'use strict'; | |
// Helpers | |
function $$(selectors, context) { | |
return (typeof selectors === 'string') ? (context || document).querySelectorAll(selectors) : [selectors]; | |
} | |
function $(selector, context) { | |
return (typeof selector === 'string') ? (context || document).querySelector(selector) : selector; | |
} | |
function create(tag, attr) { | |
var element = document.createElement(tag); | |
if(attr) { | |
for(var name in attr) { | |
if(element[name] !== undefined) { | |
element[name] = attr[name]; | |
} | |
} | |
} | |
return element; | |
} | |
function whichTransitionEnd() { | |
var root = document.documentElement; | |
var transitions = { | |
'transition' : 'transitionend', | |
'WebkitTransition' : 'webkitTransitionEnd', | |
'MozTransition' : 'mozTransitionEnd', | |
'OTransition' : 'oTransitionEnd otransitionend' | |
}; | |
for(var t in transitions){ | |
if(root.style[t] !== undefined){ | |
return transitions[t]; | |
} | |
} | |
return false; | |
} | |
function oneListener(el, type, fn, capture) { | |
capture = capture || false; | |
el.addEventListener(type, function handler(e) { | |
fn.call(this, e); | |
el.removeEventListener(e.type, handler, capture) | |
}, capture); | |
} | |
function hasClass(cls, el) { | |
return new RegExp('(^|\\s+)' + cls + '(\\s+|$)').test(el.className); | |
} | |
function addClass(cls, el) { | |
if( ! hasClass(cls, el) ) | |
return el.className += (el.className === '') ? cls : ' ' + cls; | |
} | |
function removeClass(cls, el) { | |
el.className = el.className.replace(new RegExp('(^|\\s+)' + cls + '(\\s+|$)'), ''); | |
} | |
function toggleClass(cls, el) { | |
( ! hasClass(cls, el)) ? addClass(cls, el) : removeClass(cls, el); | |
} | |
function Tags(tag) { | |
var el = $(tag); | |
if(el.instance) return; | |
el.instance = this; | |
var type = el.type; | |
var transitionEnd = whichTransitionEnd(); | |
var tagsArray = []; | |
var KEYS = { | |
ENTER: 13, | |
COMMA: 188, | |
BACK: 8 | |
}; | |
var isPressed = false; | |
var timer; | |
var wrap; | |
var field; | |
function init() { | |
// create and add wrapper | |
wrap = create('div', { | |
'className': 'tags-container', | |
}); | |
field = create('input', { | |
'type': 'text', | |
'className': 'tag-input', | |
'placeholder': el.placeholder || '' | |
}); | |
wrap.appendChild(field); | |
if(el.value.trim() !== '') { | |
hasTags(); | |
} | |
el.type = 'hidden'; | |
el.parentNode.insertBefore(wrap, el.nextSibling); | |
wrap.addEventListener('click', btnRemove, false); | |
wrap.addEventListener('keydown', keyHandler, false); | |
wrap.addEventListener('keyup', backHandler, false); | |
} | |
function hasTags() { | |
var arr = el.value.trim().split(','); | |
arr.forEach(function(item) { | |
item = item.trim(); | |
if(~tagsArray.indexOf(item)) { | |
return; | |
} | |
var tag = createTag(item); | |
tagsArray.push(item); | |
wrap.insertBefore(tag, field); | |
}); | |
} | |
function createTag(name) { | |
var tag = create('div', { | |
'className': 'tag', | |
'innerHTML': '<span class="tag__name">' + name + '</span>'+ | |
'<button class="tag__remove">×</button>' | |
}); | |
// var tagName = create('span', { | |
// 'className': 'tag__name', | |
// 'textContent': name | |
// }); | |
// var delBtn = create('button', { | |
// 'className': 'tag__remove', | |
// 'innerHTML': '×' | |
// }); | |
// tag.appendChild(tagName); | |
// tag.appendChild(delBtn); | |
return tag; | |
} | |
function btnRemove(e) { | |
e.preventDefault(); | |
if(e.target.className === 'tag__remove') { | |
var tag = e.target.parentNode; | |
var name = $('.tag__name', tag); | |
wrap.removeChild(tag); | |
tagsArray.splice(tagsArray.indexOf(name.textContent), 1); | |
el.value = tagsArray.join(',') | |
} | |
field.focus(); | |
} | |
function keyHandler(e) { | |
if(e.target.tagName === 'INPUT' && e.target.className === 'tag-input') { | |
var target = e.target; | |
var code = e.which || e.keyCode; | |
if(field.previousSibling && code !== KEYS.BACK) { | |
removeClass('tag--marked', field.previousSibling); | |
} | |
var name = target.value.trim(); | |
// if(code === KEYS.ENTER || code === KEYS.COMMA) { | |
if(code === KEYS.ENTER) { | |
target.blur(); | |
addTag(name); | |
if(timer) clearTimeout(timer); | |
timer = setTimeout(function() { target.focus(); }, 10 ); | |
} | |
else if(code === KEYS.BACK) { | |
if(e.target.value === '' && !isPressed) { | |
isPressed = true; | |
removeTag(); | |
} | |
} | |
} | |
} | |
function backHandler(e) { | |
isPressed = false; | |
} | |
function addTag(name) { | |
// delete comma if comma exists | |
name = name.toString().replace(/,/g, '').trim(); | |
if(name === '') return field.value = ''; | |
if(~tagsArray.indexOf(name)) { | |
var exist = $$('.tag', wrap); | |
Array.prototype.forEach.call(exist, function(tag) { | |
if(tag.firstChild.textContent === name) { | |
addClass('tag--exists', tag); | |
if(transitionEnd) { | |
oneListener(tag, transitionEnd, function() { | |
removeClass('tag--exists', tag); | |
}); | |
} else { | |
removeClass('tag--exists', tag); | |
} | |
} | |
}); | |
return field.value = ''; | |
} | |
var tag = createTag(name); | |
wrap.insertBefore(tag, field); | |
tagsArray.push(name); | |
field.value = ''; | |
el.value += (el.value === '') ? name : ',' + name; | |
} | |
function removeTag() { | |
if(tagsArray.length === 0) return; | |
var tags = $$('.tag', wrap); | |
var tag = tags[tags.length - 1]; | |
if( ! hasClass('tag--marked', tag) ) { | |
addClass('tag--marked', tag); | |
return; | |
} | |
tagsArray.pop(); | |
wrap.removeChild(tag); | |
el.value = tagsArray.join(','); | |
} | |
init(); | |
/* Public API */ | |
this.getTags = function() { | |
return tagsArray; | |
} | |
this.clearTags = function() { | |
if(!el.instance) return; | |
tagsArray.length = 0; | |
el.value = ''; | |
wrap.innerHTML = ''; | |
wrap.appendChild(field); | |
} | |
this.addTags = function(name) { | |
if(!el.instance) return; | |
if(Array.isArray(name)) { | |
for(var i = 0, len = name.length; i < len; i++) { | |
addTag(name[i]) | |
} | |
} else { | |
addTag(name); | |
} | |
return tagsArray; | |
} | |
this.destroy = function() { | |
if(!el.instance) return; | |
wrap.removeEventListener('click', btnRemove, false); | |
wrap.removeEventListener('keydown', keyHandler, false); | |
wrap.removeEventListener('keyup', keyHandler, false); | |
wrap.parentNode.removeChild(wrap); | |
tagsArray = null; | |
timer = null; | |
wrap = null; | |
field = null; | |
transitionEnd = null; | |
delete el.instance; | |
el.type = type; | |
} | |
} | |
window.Tags = Tags; | |
})(); | |
// Use | |
var tags = new Tags('.tagged'); | |
document.getElementById('get').addEventListener('click', function(e) { | |
e.preventDefault(); | |
alert(tags.getTags()); | |
}); | |
document.getElementById('clear').addEventListener('click', function(e) { | |
e.preventDefault(); | |
tags.clearTags(); | |
}); | |
document.getElementById('add').addEventListener('click', function(e) { | |
e.preventDefault(); | |
tags.addTags('New'); | |
}); | |
document.getElementById('addArr').addEventListener('click', function(e) { | |
e.preventDefault(); | |
tags.addTags(['Steam Machines', 'Nintendo Wii U', 'Shield Portable']); | |
}); | |
document.getElementById('destroy').addEventListener('click', function(e) { | |
e.preventDefault(); | |
if(this.textContent === 'destroy') { | |
tags.destroy(); | |
this.textContent = 'reinit'; | |
} else { | |
this.textContent = 'destroy'; | |
tags = new Tags('.tagged'); | |
} | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//=== body styles | |
body { | |
padding: 20px; | |
font-family: "Segoe UI",Roboto,"Helvetica Neue",Helvetica,Arial,sans-serif; | |
} | |
form { | |
margin-bottom: 30px; | |
} | |
.form-control { | |
display: block; | |
width: 100%; | |
font-size: 14px; | |
height: 34px; | |
padding: 4px 8px; | |
margin-bottom: 15px; | |
} | |
//=== Tags | |
$enable-flex: true; | |
$tags-color: #fff; | |
$primary-color: #317CAF; | |
$marked-color: lighten($primary-color, 20%); | |
$exist-color: #EDB5A1; | |
$container-border: 1px solid #ccc; | |
$container-bg: transparent; | |
$container-line-height: 1.6; | |
$container-font-size: 14px; | |
$container-radius: 1px; | |
$container-min-height: 34px; | |
$container-padding: 2px 5px; | |
$tags-marging: 2px 6px 2px 0; | |
$tags-padding: 1px 20px 1px 8px; | |
$tags-weight: 400; | |
$tags-radius: 3px; | |
$btn-outline-color: #fff; | |
$spacer: 15px; | |
*, *:before, *:after { | |
box-sizing: border-box; | |
} | |
.tags-container { | |
@if($enable-flex) { | |
display: flex; | |
flex-flow: row wrap; | |
} | |
margin-bottom: $spacer; | |
width: 100%; | |
min-height: $container-min-height; | |
padding: $container-padding; | |
font-size: $container-font-size; | |
line-height: $container-line-height; | |
background-color: $container-bg; | |
border: $container-border; | |
border-radius: $container-radius; | |
overflow: hidden; | |
word-wrap: break-word; | |
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); | |
} | |
input.tag-input { | |
@if ($enable-flex) { | |
flex: 3; | |
} | |
@else { | |
display: inline-block; | |
vertical-align: middle; | |
} | |
border: 0; | |
outline: 0; | |
} | |
.tag { | |
@if ($enable-flex == false) { | |
display: inline-block; | |
vertical-align: middle; | |
} | |
position: relative; | |
margin: $tags-marging; | |
padding: $tags-padding; | |
font-size: inherit; | |
font-weight: $tags-weight; | |
text-align: center; | |
color: $tags-color; | |
background-color: $primary-color; | |
border-radius: $tags-radius; | |
transition: background-color .3s ease; | |
cursor: default; | |
&:first-child { | |
margin-left: 0; | |
} | |
&--marked { | |
background-color: $marked-color; | |
} | |
&--exists { | |
background-color: $exist-color; | |
animation: shake 1s linear; | |
} | |
&__name { | |
margin-right: 3px; | |
} | |
} | |
.tag__remove { | |
position: absolute; | |
right: 0; | |
bottom: 0; | |
width: 20px; | |
height: 100%; | |
padding: 0 5px; | |
font-size: 16px; | |
font-weight: 400; | |
transition: opacity .3s ease; | |
opacity: .5; | |
cursor: pointer; | |
border: 0; | |
background-color: transparent; | |
color: #fff; | |
line-height: 1; | |
&:hover { | |
opacity: 1; | |
} | |
&:focus { | |
outline: 5px auto $btn-outline-color; | |
} | |
} | |
@keyframes shake { | |
0%, 100% { | |
transform: translate3d(0, 0, 0); | |
} | |
10%, 30%, 50%, 70%, 90% { | |
transform: translate3d(-5px, 0, 0); | |
} | |
20%, 40%, 60%, 80% { | |
transform: translate3d(5px, 0, 0); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment