Skip to content

Instantly share code, notes, and snippets.

@bmatusiak
Last active December 17, 2015 17:39
Show Gist options
  • Save bmatusiak/5648051 to your computer and use it in GitHub Desktop.
Save bmatusiak/5648051 to your computer and use it in GitHub Desktop.
.clearfix {
*zoom: 1;
}
.clearfix:before,
.clearfix:after {
display: table;
line-height: 0;
content: "";
}
.clearfix:after {
clear: both;
}
.hide-text {
font: 0/0 a;
color: transparent;
text-shadow: none;
background-color: transparent;
border: 0;
}
.input-block-level {
display: block;
width: 100%;
min-height: 30px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
input[data-provide=tokenizer] {
color: transparent;
}
div.tokenizer {
display: inline-block;
height: auto;
/*
padding: 4px 0px;
white-space: nowrap;
*/
margin: 0 0 10px 0;
vertical-align: middle;
cursor: text;
background-color: white;
border: 1px solid #ccc;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
div.tokenizer.focused {
border-color: rgba(82, 168, 236, 0.8);
outline: 0;
outline: thin dotted \9;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
}
div.tokenizer > div {
/*overflow: hidden;*/
}
div.tokenizer ul {
display: inline-block;
margin: 0;
}
div.tokenizer ul li {
display: inline-block;
margin: 3px;
}
div.tokenizer ul li:last-child {
margin-right: 0;
}
div.tokenizer ul li .input {
display: inline-block;
height: 18px;
min-width: 40px;
color: #555555;
white-space: nowrap;
outline: 0;
}
div.tokenizer li span {
vertical-align: top !important;
}
div.tokenizer li .label > i {
margin-left: 3px;
cursor: pointer;
}
.control-group.warning div.tokenizer {
color: #c09853;
border-color: #c09853;
}
.control-group.warning div.tokenizer.focused {
border-color: #a47e3c;
-webkit-box-shadow: 0 0 6px #dbc59e;
-moz-box-shadow: 0 0 6px #dbc59e;
box-shadow: 0 0 6px #dbc59e;
}
.control-group.error div.tokenizer {
color: #b94a48;
border-color: #b94a48;
}
.control-group.error div.tokenizer.focused {
border-color: #953b39;
-webkit-box-shadow: 0 0 6px #d59392;
-moz-box-shadow: 0 0 6px #d59392;
box-shadow: 0 0 6px #d59392;
}
.control-group.success div.tokenizer {
color: #468847;
border-color: #468847;
}
.control-group.success div.tokenizer.focused {
border-color: #356635;
-webkit-box-shadow: 0 0 6px #7aba7b;
-moz-box-shadow: 0 0 6px #7aba7b;
box-shadow: 0 0 6px #7aba7b;
}
(function ($) {
"use strict";
var PubSub = function () {
this.topics = {};
this.id = 0;
},
Tokenizer = function (element, options) {
this.$formInput = $(element);
this.channel = new PubSub();
this.options = $.extend({}, $.fn.tokenizer.defaults, options);
this.initialize(element);
},
List = function (channel) {
this.channel = channel;
this.initialize();
},
Item = function (channel, value) {
this.channel = channel;
this.value = value;
this.initialize();
},
Input = function (channel, delimiters) {
this.channel = channel;
this.delimiters = delimiters;
this.initialize();
};
PubSub.prototype = {
subscribe: function (topic, callback) {
if (!this.topics[topic]) {
this.topics[topic] = {};
}
this.topics[topic][this.id] = callback;
return this.id++;
},
publish: function (topic, args) {
for (var id in this.topics[topic]) {
this.topics[topic][id].apply(this, [].concat(args));
}
}
};
Tokenizer.prototype = {
constructor: Tokenizer,
initialize: function () {
this.input = new Input(this.channel, this.options.delimiters);
this.list = new List(this.channel)
.add(this.input);
this.$formInput.hide();
var self = this;
this.$formInput.change(function(){
self.parseFormInput();
});
this.$element = $('<div class="tokenizer"></div>')
.append(this.list.$element)
.on('click', $.proxy(this.handleFocus, this))
.css("width",this.$formInput.css("width") || this.$formInput.width())
.insertAfter(this.$formInput);
this.parseFormInput();
this.channel.subscribe('add', $.proxy(this.handleAdd, this));
this.channel.subscribe('blur', $.proxy(this.handleBlur, this));
this.channel.subscribe('remove', $.proxy(this.handleRemove, this));
},
add: function (value) {
if (value) {
var item = new Item(this.channel, value),
index = this.list.indexOf(this.input);
this.list.add(item, index);
this.updateFormInput();
}
return this;
},
handleAdd: function (value) {
this.add(value);
this.input.blur().focus();
},
handleBlur: function () {
this.$element.removeClass('focused');
},
handleFocus: function () {
this.$element.addClass('focused');
this.input.focus();
},
handleRemove: function (item) {
this.remove(item);
this.input.blur().focus();
},
parseFormInput: function () {
var item;
while((item = this.list.getPreceding(this.input))){
this.list.remove(item);
}
var values = this.$formInput.val().split(this.options.separator);
for (var i = 0; i < values.length; ++i) {
this.add(values[i]);
}
this.input.focus().blur();
},
remove: function (item) {
if (!item) {
item = this.list.getPreceding(this.input);
}
if (item) {
this.list.remove(item);
this.updateFormInput();
}
return this;
},
updateFormInput: function () {
this.$formInput.attr('value', this.list.values().join(this.options.separator));
}
};
List.prototype = {
constructor: List,
initialize: function () {
this.$list = $('<ul></ul>');
this.$element = $('<div></div>')
.append(this.$list);
this.items = [];
this.views = [];
},
add: function (item, index) {
var view = $('<li></li>').append(item.$element);
if (index >= 0) {
view.insertBefore(this.views[index]);
this.items.splice(index, 0, item);
this.views.splice(index, 0, view);
} else {
this.$list.append(view);
this.items.push(item);
this.views.push(view);
}
return this;
},
getPreceding: function (item) {
return this.items[$.inArray(item, this.items) - 1];
},
indexOf: function (item) {
return $.inArray(item, this.items);
},
remove: function (item) {
var index = $.inArray(item, this.items);
if (index >= 0) {
this.items.splice(index, 1);
this.views.splice(index, 1)[0].remove();
}
return this;
},
values: function () {
return $.map(this.items, function (i){ return i.value; });
}
};
Input.prototype = {
constructor: Input,
initialize: function () {
this.$element = $('<span class="input" contenteditable="true"></span>')
.on('blur', $.proxy(this.handleBlur, this))
.on('keydown', $.proxy(this.handleKeydown, this));
},
blur: function () {
this.$element.trigger('blur');
return this;
},
clearValue: function () {
var value = this.$element.text();
this.$element.text('');
return value;
},
focus: function () {
this.$element.trigger('focus');
return this;
},
handleBlur: function (event) {
this.channel.publish('blur');
if(this.$element.text() !== "")
this.channel.publish('add', this.clearValue());
},
isEmpty: function () {
return !this.$element.text();
},
handleKeydown: function (event) {
if ($.inArray(event.keyCode, this.delimiters) > -1) {
event.stopPropagation();
event.preventDefault();
this.channel.publish('add', this.clearValue());
}
else if (event.keyCode === 8 && this.isEmpty()) {
this.channel.publish('remove');
}
}
};
Item.prototype = {
constructor: Item,
initialize: function () {
this.$icon = $('<i class="icon-remove icon-white"></i>')
.on('click', $.proxy(this.handleRemoveClick, this));
this.$element = $('<span class="label"></span>')
.append(this.value)
.append(this.$icon);
},
handleRemoveClick: function (event) {
this.channel.publish('remove', this);
}
};
/* TOKENIZER PLUGIN DEFINITION
* =========================== */
$.fn.tokenizer = function ( option ) {
return this.filter('input').each(function () {
var $this = $(this),
data = $this.data('tokenizer'),
options = typeof option == 'object' && option;
if (!data) {
$this.data('tokenizer', (data = new Tokenizer(this, options)));
}
if (typeof option == 'string') {
data[option]();
}
});
};
$.fn.tokenizer.defaults = {
separator: ',',
delimiters: [13, 32, 188] // [enter, space, comma]
};
$.fn.tokenizer.Constructor = Tokenizer;
/* TOKENIZER DATA-API
* ================== */
$(function () {
$('input[data-provide="tokenizer"]').each(function () {
var $element = $(this);
if ($element.data('tokenizer')) {
return;
}
$element.tokenizer($element.data());
});
});
})(window.jQuery);
@bmatusiak
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment