528 lines
16 KiB
JavaScript
528 lines
16 KiB
JavaScript
|
/*
|
||
|
* popselect - v0.1.14
|
||
|
* Replaces traditional <select> with a options from popover
|
||
|
* http://jquer.in/popselect
|
||
|
*
|
||
|
* Made by Jay Kanakiya
|
||
|
* Under MIT License
|
||
|
*/
|
||
|
;(function($, window, document, undefined) {
|
||
|
|
||
|
'use strict';
|
||
|
|
||
|
// Create the defaults once
|
||
|
var pluginName = 'popSelect';
|
||
|
var defaults = {
|
||
|
position: 'top',
|
||
|
showTitle: true,
|
||
|
autoIncrease: true,
|
||
|
title: 'Select Multiple Options',
|
||
|
debug: false,
|
||
|
maxAllowed: 0,
|
||
|
placeholderText: 'Click to Add Values',
|
||
|
autofocus: false
|
||
|
};
|
||
|
|
||
|
var classNames = {
|
||
|
tag: 'tag',
|
||
|
arrow: 'arrow',
|
||
|
selectWrapper: 'popover-select-wrapper',
|
||
|
tagWrapper: 'popover-tag-wrapper',
|
||
|
popoverSelect: 'popover-select',
|
||
|
popoverBody: 'popover-select-body',
|
||
|
selectTextarea: 'popover-select-textarea',
|
||
|
selectTags: 'popover-select-tags',
|
||
|
popoverClose: 'popSelect-close',
|
||
|
selectList: 'popover-select-list',
|
||
|
popoverDisabled: 'disabled',
|
||
|
placeholder: 'placeholder',
|
||
|
placeholderInput: 'placeholder input',
|
||
|
placeholderText: 'placeholder-text',
|
||
|
selectTitle: 'popover-select-title',
|
||
|
top: 'top'
|
||
|
};
|
||
|
|
||
|
var logs = {
|
||
|
popoverGenerated: 'PopSelect Code Generated',
|
||
|
closeClicked: 'Close button clicked',
|
||
|
noElem: 'No element to be removed',
|
||
|
unSupported: 'Not Supported',
|
||
|
posChanged: 'Position changed'
|
||
|
};
|
||
|
|
||
|
var constants = {
|
||
|
option: 'option',
|
||
|
blur: 'blur',
|
||
|
click: 'click',
|
||
|
mousedown: 'mousedown',
|
||
|
li: 'li',
|
||
|
attrVal: 'data-value',
|
||
|
attrText: 'data-text',
|
||
|
body: 'BODY'
|
||
|
};
|
||
|
|
||
|
// The actual plugin constructor
|
||
|
function Plugin(element, options) {
|
||
|
this.element = element;
|
||
|
// jQuery has an extend method which merges the contents of two or
|
||
|
// more objects, storing the result in the first object. The first object
|
||
|
// is generally empty as we don't want to alter the default options for
|
||
|
// future instances of the plugin
|
||
|
this.settings = $.extend({}, defaults, options);
|
||
|
this._defaults = defaults;
|
||
|
this._name = pluginName;
|
||
|
this.init();
|
||
|
}
|
||
|
|
||
|
// Avoid Plugin.prototype conflicts
|
||
|
$.extend(Plugin.prototype, {
|
||
|
init: function() {
|
||
|
var $this = this;
|
||
|
this.$elem = $(this.element);
|
||
|
|
||
|
// Get all the options in an array
|
||
|
this.$options = this.$elem.children(constants.option).map(function(i, option) {
|
||
|
return {
|
||
|
val: $(option).val(),
|
||
|
text: $(option).text(),
|
||
|
selected: $(option).attr('selected')
|
||
|
};
|
||
|
});
|
||
|
|
||
|
// Wrap the whole input box in your own popover
|
||
|
this.$elem.wrap(template(createEmptyDiv(), {
|
||
|
wrapper: classNames.selectWrapper
|
||
|
}));
|
||
|
|
||
|
var elemPos = this.getPosition(this.$elem);
|
||
|
this.elemPos = elemPos;
|
||
|
|
||
|
// Also Add the required css Properties
|
||
|
this.$elem
|
||
|
.parent(addDot(classNames.selectWrapper))
|
||
|
.css({width: this.settings.width || elemPos.width, height: elemPos.height});
|
||
|
|
||
|
// Append the popover to $elem
|
||
|
var popUpCode = this.generatePopover(this.$options);
|
||
|
$this.log(logs.popoverGenerated, popUpCode);
|
||
|
this.$elem.after(popUpCode);
|
||
|
|
||
|
// Assign the $popover to the new $elem
|
||
|
this.$popover = this.$elem.next(addDot(classNames.popoverSelect));
|
||
|
this.$popover.css({top: 0, left: 0});
|
||
|
|
||
|
// Append Tagging System to it
|
||
|
this.$elem.after(createTaggingStr(this.settings.placeholderText, this.$options));
|
||
|
|
||
|
// Get the Tag Wrapper for later use
|
||
|
this.$tagWrapper = this.$elem.next(addDot(classNames.tagWrapper));
|
||
|
this.baseHeight = this.$tagWrapper.height();
|
||
|
|
||
|
// Get the input
|
||
|
this.$inputTagField = this.$tagWrapper.find(addDot(classNames.selectTextarea));
|
||
|
|
||
|
// Hide the popover when blurring the inputTagField
|
||
|
this.$inputTagField.on(constants.blur, function() {
|
||
|
$this.$popover.hide();
|
||
|
});
|
||
|
|
||
|
// Get the tags in the wrapper
|
||
|
this.$tags = this.$tagWrapper.find(addDot(classNames.selectTags));
|
||
|
|
||
|
// Show Popover on click of tags
|
||
|
this.$tags
|
||
|
.on(constants.click, this.initializePopover.bind(this));
|
||
|
|
||
|
// Also Attach to placeHolder Text
|
||
|
this.$tags.next(addDot(classNames.placeholderText))
|
||
|
.on(constants.click, this.initializePopover.bind(this));
|
||
|
|
||
|
// Attach Event Listener to ul list
|
||
|
this.$tags.on(constants.click, addDot(classNames.popoverClose), function() {
|
||
|
$this.inputToPopover($(this));
|
||
|
});
|
||
|
|
||
|
// Attach List Event Handlers to Li
|
||
|
this.$popover.find(addDot(classNames.selectList)).on(constants.mousedown, function(e) {
|
||
|
e.preventDefault();
|
||
|
}).on(constants.click, constants.li, function() {
|
||
|
$this.popoverToInput($(this));
|
||
|
});
|
||
|
|
||
|
// Finally Hide the Element
|
||
|
this.$elem.hide();
|
||
|
|
||
|
// Required for placeholdertext and pre-selected values
|
||
|
this.checkNumberOfTags();
|
||
|
|
||
|
// If pre-selected are higher than normal
|
||
|
this.changeSize();
|
||
|
|
||
|
// Trigger init event
|
||
|
this.$elem.trigger('popselect:init');
|
||
|
|
||
|
if (this.settings.autofocus) {
|
||
|
this.initializePopover();
|
||
|
}
|
||
|
},
|
||
|
inputToPopover: function($elem) {
|
||
|
var $li = $elem.parent();
|
||
|
this.log(logs.closeClicked, $li);
|
||
|
var val = $li.attr(constants.attrVal);
|
||
|
var text = $li.attr(constants.attrText);
|
||
|
|
||
|
// Remove them from input and add it to popover
|
||
|
this.appendToPopup(val, text);
|
||
|
$li.remove();
|
||
|
|
||
|
// Standard Reset Calls
|
||
|
this.setPlaceholder();
|
||
|
this.focus();
|
||
|
|
||
|
// Whether to increase/decrease width
|
||
|
this.changeSize();
|
||
|
|
||
|
// Whether to enable / disable popover and Placeholder Text
|
||
|
this.checkNumberOfTags();
|
||
|
|
||
|
// Trigger remove event, passing value and text of removed tag
|
||
|
this.$elem.trigger('popselect:remove', [val, text]);
|
||
|
},
|
||
|
enablePopover: function() {
|
||
|
this.$popover.find(addDot(classNames.selectList) + ' li')
|
||
|
.removeClass(classNames.popoverDisabled);
|
||
|
},
|
||
|
disablePopover: function() {
|
||
|
this.$popover.find(addDot(classNames.selectList) + ' li')
|
||
|
.addClass(classNames.popoverDisabled);
|
||
|
},
|
||
|
checkNumberOfTags: function() {
|
||
|
var currentNo = this.$tags.find(addDot(classNames.tag)).length;
|
||
|
if (currentNo === 0) {
|
||
|
this.enablePlaceHolderText();
|
||
|
} else {
|
||
|
this.disablePlaceHolderText();
|
||
|
}
|
||
|
|
||
|
if (this.settings.maxAllowed !== 0) {
|
||
|
if (this.settings.maxAllowed > currentNo) {
|
||
|
this.enablePopover();
|
||
|
} else {
|
||
|
this.disablePopover();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.syncWithSelect();
|
||
|
},
|
||
|
popoverToInput: function($elem) {
|
||
|
var val = $elem.attr(constants.attrVal);
|
||
|
var text = $elem.text();
|
||
|
var li = createTagStr(val, text);
|
||
|
|
||
|
// Remove them from popover and it to input
|
||
|
this.$tags.append(li);
|
||
|
$elem.remove();
|
||
|
|
||
|
// Standard Reset Calls
|
||
|
this.setPlaceholder();
|
||
|
this.focus();
|
||
|
this.popoverShow();
|
||
|
this.changePosition();
|
||
|
|
||
|
// Whether to increase/decrease width
|
||
|
this.changeSize();
|
||
|
|
||
|
// Enable / Disable Popover
|
||
|
this.checkNumberOfTags();
|
||
|
|
||
|
// Trigger add event, passing value and text of added tag
|
||
|
this.$elem.trigger('popselect:add', [val, text]);
|
||
|
},
|
||
|
popoverShow: function() {
|
||
|
// Change Position as well show popover
|
||
|
if (this.$popover.find(addDot(classNames.selectList) + ' li').length) {
|
||
|
this.$popover.show();
|
||
|
} else {
|
||
|
this.$popover.hide();
|
||
|
}
|
||
|
},
|
||
|
initializePopover: function() {
|
||
|
this.popoverShow();
|
||
|
this.changePosition();
|
||
|
this.setPlaceholder();
|
||
|
this.focus();
|
||
|
},
|
||
|
enablePlaceHolderText: function() {
|
||
|
this.$tags.next(addDot(classNames.placeholderText)).show();
|
||
|
},
|
||
|
disablePlaceHolderText: function() {
|
||
|
this.$tags.next(addDot(classNames.placeholderText)).hide();
|
||
|
},
|
||
|
focus: function() {
|
||
|
var $this = this;
|
||
|
this.$tags.find(addDot(classNames.placeholderInput)).focus();
|
||
|
this.$tags.find(addDot(classNames.placeholderInput)).on(constants.blur, function() {
|
||
|
$this.$popover.hide();
|
||
|
});
|
||
|
},
|
||
|
setPlaceholder: function() {
|
||
|
if (this.$tags.children(addDot(classNames.placeholder)).length) {
|
||
|
this.$tags.children(addDot(classNames.placeholder)).remove();
|
||
|
}
|
||
|
this.$tags.append(createPlaceholderInput());
|
||
|
this.disableInput();
|
||
|
},
|
||
|
disableInput: function() {
|
||
|
var $this = this;
|
||
|
this.$tags.find(addDot(classNames.placeholderInput)).keyup(function(e) {
|
||
|
// Empty the input always
|
||
|
$(this).val('');
|
||
|
|
||
|
// For delete key, backspace and Ctrl + x Key
|
||
|
if (e.which === 8 || e.which === 46 || e.ctrlKey && e.which === 88) {
|
||
|
$this.removeLastElem();
|
||
|
}
|
||
|
});
|
||
|
},
|
||
|
changeSize: function() {
|
||
|
if (this.settings.autoIncrease) {
|
||
|
var tagWidth = 0;
|
||
|
var textWidth = this.settings.width || this.elemPos.width;
|
||
|
this.$tags.find(addDot(classNames.tag)).each(function(i, elem) {
|
||
|
tagWidth += $(elem).outerWidth() + 20;
|
||
|
});
|
||
|
var mHeight = Math.floor(tagWidth / textWidth);
|
||
|
this.$tags.height((mHeight + 1) * this.baseHeight);
|
||
|
}
|
||
|
},
|
||
|
removeLastElem: function() {
|
||
|
// Delete the last selected li if present
|
||
|
var tags = this.$tags.find(addDot(classNames.tag));
|
||
|
if (tags.length) {
|
||
|
var $li = $(tags[tags.length - 1]);
|
||
|
var val = $li.attr(constants.attrVal);
|
||
|
var text = $li.attr(constants.attrText);
|
||
|
|
||
|
// Remove them from input and add it to popover
|
||
|
this.appendToPopup(val, text);
|
||
|
$li.remove();
|
||
|
|
||
|
// Standard Reset Calls
|
||
|
this.changePosition();
|
||
|
this.setPlaceholder();
|
||
|
this.focus();
|
||
|
|
||
|
// Whether to increase/decrease width
|
||
|
this.changeSize();
|
||
|
|
||
|
// Enable / Disable Popover
|
||
|
this.checkNumberOfTags();
|
||
|
} else {
|
||
|
this.log(logs.noElem);
|
||
|
}
|
||
|
},
|
||
|
setTitle: function(title) {
|
||
|
if (this.settings.showTitle) {
|
||
|
this.$popover.find(addDot(classNames.selectTitle)).text(title);
|
||
|
}
|
||
|
},
|
||
|
getPosition: function($element) {
|
||
|
$element = $element || this.$element;
|
||
|
var el = $element[0];
|
||
|
var isBody = el.tagName === constants.body;
|
||
|
|
||
|
var elRect = el.getBoundingClientRect();
|
||
|
if (elRect.width == null) {
|
||
|
var w = elRect.right - elRect.left;
|
||
|
var h = elRect.bottom - elRect.top;
|
||
|
elRect = $.extend({}, elRect, {width: w, height: h});
|
||
|
}
|
||
|
var elOffset = isBody ? {top: 0, left: 0} : $element.offset();
|
||
|
/* jshint ignore:start */
|
||
|
var scroll = {scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }
|
||
|
/* jshint ignore:end */
|
||
|
var outerDims = isBody ? {width: $(window).width(), height: $(window).height()} : null;
|
||
|
|
||
|
return $.extend({}, elRect, scroll, outerDims, elOffset);
|
||
|
},
|
||
|
syncWithSelect: function() {
|
||
|
var arrValues = this.$tags.find(addDot(classNames.tag)).map(function(i, elem) {
|
||
|
return $(elem).attr('data-value');
|
||
|
}).toArray();
|
||
|
this.$elem.children(constants.option).each(function(i, option) {
|
||
|
if (arrValues.indexOf($(option).val()) < 0) {
|
||
|
$(option).removeAttr('selected');
|
||
|
} else {
|
||
|
$(option).attr('selected', 'selected');
|
||
|
}
|
||
|
});
|
||
|
},
|
||
|
appendToPopup: function(val, text) {
|
||
|
var li = createLiTag(val, text);
|
||
|
this.$popover.find(addDot(classNames.selectList)).append(li);
|
||
|
},
|
||
|
generatePopover: function(options) {
|
||
|
var list = '';
|
||
|
for (var i = 0; i < options.length; i++) {
|
||
|
if (!options[i].selected) {
|
||
|
list += createLiTag(options[i].val, options[i].text);
|
||
|
}
|
||
|
}
|
||
|
var popoverStr = createPopoverStr(list, this.settings);
|
||
|
return popoverStr;
|
||
|
},
|
||
|
changePosition: function() {
|
||
|
// It first needs to be placed
|
||
|
var popPos = this.getPosition(this.$popover);
|
||
|
var tagPos = this.getPosition(this.$tagWrapper);
|
||
|
|
||
|
var leftOffset = ((this.settings.width || this.elemPos.width) / 2) - (popPos.width / 2);
|
||
|
var topOffset;
|
||
|
if (this.settings.position === 'top') {
|
||
|
topOffset = -(popPos.height);
|
||
|
} else {
|
||
|
topOffset = tagPos.height;
|
||
|
}
|
||
|
|
||
|
this.log('popPos.width', popPos.width);
|
||
|
this.log(logs.posChanged, topOffset, leftOffset);
|
||
|
this.$popover.css({top: topOffset, left: leftOffset});
|
||
|
},
|
||
|
log: function() {
|
||
|
if (this.settings.debug) {
|
||
|
console.log.apply(console, arguments);
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* A quick helper function for creating templates
|
||
|
* @param {string} s Template String
|
||
|
* @param {object} d Values to replace for
|
||
|
* @return {string} Populated template string
|
||
|
*/
|
||
|
function template(s, d) {
|
||
|
for (var p in d) {
|
||
|
s = s.replace(new RegExp('{' + p + '}', 'g'), d[p]);
|
||
|
}
|
||
|
return s;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Just adds a dot for easy class selection
|
||
|
* @param {string} str DOM className
|
||
|
* @return {string} jQuery selector
|
||
|
*/
|
||
|
function addDot(str) {
|
||
|
return '.' + str;
|
||
|
}
|
||
|
|
||
|
function createEmptyDiv(x) {
|
||
|
if (x) {
|
||
|
return '<div class="{' + x + '}"></div>';
|
||
|
} else {
|
||
|
return '<div class="{wrapper}"></div>';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function createTagsLi(options) {
|
||
|
var str = '';
|
||
|
for (var i = 0; i < options.length; i++) {
|
||
|
if (options[i].selected) {
|
||
|
str += createTagStr(options[i].val, options[i].text);
|
||
|
}
|
||
|
}
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
function createTaggingStr(text, options) {
|
||
|
return template('<div class="{tagWrapper}">' +
|
||
|
'<textarea class="{selectTextarea}"></textarea>' +
|
||
|
'<ul class="{selectTags}">' +
|
||
|
'{tags}' +
|
||
|
'</ul>' +
|
||
|
'<div class="{placeholderText}">' +
|
||
|
'{text}' +
|
||
|
'</div>' +
|
||
|
'</div>', {
|
||
|
tags: createTagsLi(options),
|
||
|
text: text,
|
||
|
placeholderText: classNames.placeholderText,
|
||
|
tagWrapper: classNames.tagWrapper,
|
||
|
selectTextarea: classNames.selectTextarea,
|
||
|
selectTags: classNames.selectTags
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function createTagStr(val, text) {
|
||
|
return template('<li class="{tag}" data-value="{val}" data-text="{text}">' +
|
||
|
'<span class="{popoverClose}">×</span>{text}' +
|
||
|
'</li>', {
|
||
|
text: text,
|
||
|
val: val,
|
||
|
tag: classNames.tag,
|
||
|
popoverClose: classNames.popoverClose
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function createLiTag(val, text) {
|
||
|
return template('<li data-value="{val}" data-text="{text}">{text}</li>', {
|
||
|
val: val,
|
||
|
text: text
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function createPlaceholderInput() {
|
||
|
return template('<li class="{placeholder}">' +
|
||
|
'<div>' +
|
||
|
'<input type="text" readonly="true">' +
|
||
|
'</div>' +
|
||
|
'</li>', {
|
||
|
placeholder: classNames.placeholder
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function createPopoverStr(list, settings) {
|
||
|
return template('<div class="{popoverSelect} {top}">' +
|
||
|
(settings.showTitle ? '<h3 class="{selectTitle}">{title}</h3>' : '') +
|
||
|
'<div class="{popoverBody}">' +
|
||
|
'<ul class="{selectList}">' +
|
||
|
'{list}' +
|
||
|
'</ul>' +
|
||
|
'</div>' +
|
||
|
'<div class="{arrow}"></div>' +
|
||
|
'</div>', {
|
||
|
title: settings.title,
|
||
|
list: list,
|
||
|
arrow: classNames.arrow,
|
||
|
popoverSelect: classNames.popoverSelect,
|
||
|
popoverBody: classNames.popoverBody,
|
||
|
selectList: classNames.selectList,
|
||
|
top: settings.position,
|
||
|
selectTitle: classNames.selectTitle
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// A really lightweight plugin wrapper around the constructor,
|
||
|
// preventing against multiple instantiations
|
||
|
$.fn.popSelect = function(options) {
|
||
|
if (typeof(options) === 'string') {
|
||
|
if (options === 'value') {
|
||
|
return this.next(addDot(classNames.tagWrapper))
|
||
|
.find(addDot(classNames.tag)).map(function(i, $elem) {
|
||
|
return $($elem).attr(constants.attrVal);
|
||
|
});
|
||
|
} else {
|
||
|
console.warn(logs.unSupported);
|
||
|
}
|
||
|
} else {
|
||
|
return this.each(function() {
|
||
|
if (!$.data(this, 'plugin_' + pluginName)) {
|
||
|
$.data(this, 'plugin_' + pluginName, new Plugin(this, options));
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
};
|
||
|
|
||
|
})(jQuery, window, document);
|