metro4
Version:
The front-end framework for Build responsive, mobile-first projects on the web with the first front-end component library in Metro Style
791 lines (647 loc) • 27.4 kB
JavaScript
/* global Metro*/
(function(Metro, $) {
'use strict';
var Utils = Metro.utils;
var SelectDefaultConfig = {
id: "",
label: "",
size: "normal",
selectDeferred: 0,
clearButton: false,
clearButtonIcon: "<span class='default-icon-cross'></span>",
usePlaceholder: false,
placeholder: "",
addEmptyValue: false,
emptyValue: "",
duration: 0,
prepend: "",
append: "",
filterPlaceholder: "Search...",
filter: true,
copyInlineStyles: false,
dropHeight: 200,
dropWidth: null,
dropFullSize: false,
checkDropUp: true,
dropUp: false,
showGroupName: false,
shortTag: true,
clsSelect: "",
clsSelectInput: "",
clsPrepend: "",
clsAppend: "",
clsOption: "",
clsOptionActive: "",
clsOptionGroup: "",
clsDropList: "",
clsDropContainer: "",
clsSelectedItem: "",
clsSelectedItemRemover: "",
clsLabel: "",
clsGroupName: "",
onClear: Metro.noop,
onChange: Metro.noop,
onUp: Metro.noop,
onDrop: Metro.noop,
onItemSelect: Metro.noop,
onItemDeselect: Metro.noop,
onSelectCreate: Metro.noop
};
Metro.selectSetup = function (options) {
SelectDefaultConfig = $.extend({}, SelectDefaultConfig, options);
};
if (typeof window["metroSelectSetup"] !== undefined) {
Metro.selectSetup(window["metroSelectSetup"]);
}
Metro.Component('select', {
init: function( options, elem ) {
this._super(elem, options, SelectDefaultConfig, {
list: null,
placeholder: null,
observer: null,
});
return this;
},
_create: function(){
var element = this.element;
this._createSelect();
this._createEvents();
this._fireEvent("select-create", {
element: element
});
},
_setPlaceholder: function(){
var element = this.element, o = this.options;
var input = element.siblings(".select-input");
if (o.usePlaceholder === true && (!Utils.isValue(element.val()) || element.val() == o.emptyValue)) {
input.html(this.placeholder);
}
},
_addTag: function(val, data){
var element = this.element, o = this.options;
var tag, tagSize, container = element.closest(".select");
tag = $("<div>").addClass("tag").addClass(o.shortTag ? "short-tag" : "").addClass(o.clsSelectedItem).html("<span class='title'>"+val+"</span>").data("option", data);
$("<span>").addClass("remover").addClass(o.clsSelectedItemRemover).html("×").appendTo(tag);
if (container.hasClass("input-large")) {
tagSize = "large";
} else if (container.hasClass("input-small")) {
tagSize = "small"
}
tag.addClass(tagSize);
return tag;
},
_addOption: function(item, parent, input, multiple, group){
var option = $(item);
var l, a;
var element = this.element, o = this.options;
var html = Utils.isValue(option.attr('data-template')) ? option.attr('data-template').replace("$1", item.text):item.text;
var displayValue = option.attr("data-display");
l = $("<li>").addClass(o.clsOption).data("option", item).attr("data-text", item.text).attr('data-value', item.value ? item.value : "");
a = $("<a>").html(html);
if (displayValue) {
l.attr("data-display", displayValue);
html = displayValue;
}
l.addClass(item.className);
l.data("group", group);
if (option.is(":disabled")) {
l.addClass("disabled");
}
if (option.is(":selected")) {
if (o.showGroupName && group) {
html += " <span class='selected-item__group-name "+o.clsGroupName+"'>" + group + "</span>";
}
if (multiple) {
l.addClass("d-none");
input.append(this._addTag(html, l));
} else {
element.val(item.value);
input.html(html);
element.fire("change", {
val: item.value
});
l.addClass("active");
}
}
l.append(a).appendTo(parent);
},
_addOptionGroup: function(item, parent, input, multiple){
var that = this, o = this.options;
var group = $(item);
$("<li>").html(item.label).addClass("group-title").addClass(o.clsOptionGroup).appendTo(parent);
$.each(group.children(), function(){
that._addOption(this, parent, input, multiple, item.label);
})
},
_createOptions: function(){
var that = this, element = this.element, o = this.options, select = element.parent();
var list = select.find("ul").empty();
var selected = element.find("option[selected]").length > 0;
var multiple = element[0].multiple;
var input = element.siblings(".select-input");
element.siblings(".select-input").empty();
if (o.addEmptyValue === true) {
element.prepend($("<option "+(!selected ? 'selected' : '')+" value='"+o.emptyValue+"' class='d-none'></option>"));
}
$.each(element.children(), function(){
if (this.tagName === "OPTION") {
that._addOption(this, list, input, multiple, null);
} else if (this.tagName === "OPTGROUP") {
that._addOptionGroup(this, list, input, multiple);
}
});
},
_createSelect: function(){
var that = this, element = this.element, o = this.options;
var container = $("<label>");
var multiple = element[0].multiple;
var select_id = Utils.elementId("select");
var buttons = $("<div>").addClass("button-group");
var input, drop_container, drop_container_input, list, filter_input, dropdown_toggle;
var checkboxID = Utils.elementId("select-focus-trigger");
var checkbox = $("<input type='checkbox'>").addClass("select-focus-trigger").attr("id", checkboxID);
this.placeholder = $("<span>").addClass("placeholder").html(o.placeholder);
container.attr("id", o.id ? o.id : select_id).attr("for", checkboxID);
container[0].className = Metro.utils.classNames(
element[0].className,
"input-" + o.size,
"select",
o.clsSelect
)
dropdown_toggle = $("<span>").addClass("dropdown-toggle");
dropdown_toggle.appendTo(container);
if (multiple) {
container.addClass("multiple");
}
container.insertBefore(element);
element.appendTo(container);
buttons.appendTo(container);
checkbox.appendTo(container);
input = $("<div>").addClass("select-input").addClass(o.clsSelectInput).attr("name", "__" + select_id + "__");
drop_container = $("<div>").addClass("drop-container").addClass(o.clsDropContainer);
if (o.dropFullSize === false) {
if (o.dropWidth) {
drop_container.css({
width: +o.dropWidth
})
}
} else {
container.addClass("drop-full-size")
}
drop_container_input = $("<div>").appendTo(drop_container);
list = $("<ul>").addClass("option-list").addClass(o.clsDropList).css({
"max-height": o.dropHeight
});
filter_input = $("<input type='text' data-role='input'>").attr("placeholder", o.filterPlaceholder).appendTo(drop_container_input);
container.append(input);
container.append(drop_container);
drop_container.append(drop_container_input);
if (o.filter !== true) {
drop_container_input.hide();
}
drop_container.append(list);
this._createOptions();
this._setPlaceholder();
Metro.makePlugin(drop_container, "dropdown", {
dropFilter: ".select",
duration: o.duration,
toggleElement: [container],
checkDropUp: o.checkDropUp,
dropUp: o.dropUp,
onDrop: function(){
var dropped, target;
dropdown_toggle.addClass("active-toggle");
dropped = $(".select .drop-container");
$.each(dropped, function(){
var drop = $(this);
if (drop.is(drop_container)) {
return ;
}
var dataDrop = Metro.getPlugin(drop, 'dropdown');
if (dataDrop && dataDrop.close) {
dataDrop.close();
}
});
filter_input.val("").trigger(Metro.events.keyup);//.focus();
target = list.find("li.active").length > 0 ? $(list.find("li.active")[0]) : undefined;
if (target !== undefined) {
list[0].scrollTop = target.position().top - ( (list.height() - target.height() )/ 2);
}
that._fireEvent("drop", {
list: list[0]
});
},
onUp: function(){
dropdown_toggle.removeClass("active-toggle");
that._fireEvent("up", {
list: list[0]
});
}
});
this.list = list;
if (o.clearButton === true && !element[0].readOnly) {
var clearButton = $("<button>").addClass("button input-clear-button").addClass(o.clsClearButton).attr("tabindex", -1).attr("type", "button").html(o.clearButtonIcon);
clearButton.appendTo(buttons);
} else {
buttons.addClass("d-none");
}
if (o.prepend !== "" && !multiple) {
var prepend = $("<div>").html(o.prepend);
prepend.addClass("prepend").addClass(o.clsPrepend).appendTo(container);
}
if (o.append !== "" && !multiple) {
var append = $("<div>").html(o.append);
append.addClass("append").addClass(o.clsAppend).appendTo(container);
}
if (o.copyInlineStyles === true) {
for (var i = 0, l = element[0].style.length; i < l; i++) {
container.css(element[0].style[i], element.css(element[0].style[i]));
}
}
if (element.attr('dir') === 'rtl' ) {
container.addClass("rtl").attr("dir", "rtl");
}
if (o.label) {
var label = $("<label>").addClass("label-for-input").addClass(o.clsLabel).html(o.label).insertBefore(container);
if (element.attr("id")) {
label.attr("for", element.attr("id"));
}
if (element.attr("dir") === "rtl") {
label.addClass("rtl");
}
}
if (element.is(':disabled')) {
this.disable();
} else {
this.enable();
}
this.observer = new MutationObserver(this._updateSelect.bind(this))
this.observer.observe(element[0], {
childList: true,
subtree: true
});
},
_updateSelect: function(mutation){
for (let record of mutation) {
if (record.type === 'childList') {
if (record.addedNodes.length || record.removedNodes.length) {
this._createOptions()
}
}
}
},
_createEvents: function(){
var that = this, element = this.element, o = this.options;
var container = element.closest(".select");
var drop_container = container.find(".drop-container");
var input = element.siblings(".select-input");
var filter_input = drop_container.find("input");
var list = drop_container.find("ul");
var clearButton = container.find(".input-clear-button");
var checkbox = container.find(".select-focus-trigger");
checkbox.on("focus", function(){
container.addClass("focused");
});
checkbox.on("blur", function(){
container.removeClass("focused");
});
clearButton.on(Metro.events.click, function(e){
element.val(o.emptyValue);
if (element[0].multiple) {
list.find("li").removeClass("d-none");
}
input.clear();
that._setPlaceholder();
e.preventDefault();
e.stopPropagation();
that._fireEvent("clear");
that._fireEvent("change", {
selected: that.getSelected()
});
});
element.on(Metro.events.change, function(){
that._setPlaceholder();
});
container.on(Metro.events.click, function(){
$(".focused").removeClass("focused");
container.addClass("focused");
});
input.on(Metro.events.click, function(){
$(".focused").removeClass("focused");
container.addClass("focused");
});
list.on(Metro.events.click, "li", function(e){
if ($(this).hasClass("group-title")) {
e.preventDefault();
e.stopPropagation();
return ;
}
var leaf = $(this);
var displayValue = leaf.attr("data-display");
var val = leaf.data('value');
var group = leaf.data('group');
var html = displayValue ? displayValue : leaf.children('a').html();
var selected;
var option = leaf.data("option");
var options = element.find("option");
if (o.showGroupName && group) {
html += " <span class='selected-item__group-name "+o.clsGroupName+"'>" + group + "</span>";
}
if (element[0].multiple) {
leaf.addClass("d-none");
input.append(that._addTag(html, leaf));
} else {
list.find("li.active").removeClass("active").removeClass(o.clsOptionActive);
leaf.addClass("active").addClass(o.clsOptionActive);
input.html(html);
Metro.getPlugin(drop_container, "dropdown").close();
}
$.each(options, function(){
if (this === option) {
this.selected = true;
}
});
that._fireEvent("item-select", {
val: val,
option: option,
leaf: leaf[0]
});
selected = that.getSelected();
that._fireEvent("change", {
selected: selected
});
});
input.on("click", ".tag .remover", function(e){
var item = $(this).closest(".tag");
var leaf = item.data("option");
var option = leaf.data('option');
var selected;
leaf.removeClass("d-none");
$.each(element.find("option"), function(){
if (this === option) {
this.selected = false;
}
});
item.remove();
that._fireEvent("item-deselect", {
option: option
});
selected = that.getSelected();
that._fireEvent("change", {
selected: selected
});
e.preventDefault();
e.stopPropagation();
});
filter_input.on(Metro.events.keyup, function(){
var filter = this.value.toUpperCase();
var li = list.find("li");
var i, a;
for (i = 0; i < li.length; i++) {
if ($(li[i]).hasClass("group-title")) continue;
a = li[i].getElementsByTagName("a")[0];
if (a.innerHTML.toUpperCase().indexOf(filter) > -1) {
li[i].style.display = "";
} else {
li[i].style.display = "none";
}
}
});
filter_input.on(Metro.events.click, function(e){
e.preventDefault();
e.stopPropagation();
});
drop_container.on(Metro.events.click, function(e){
e.preventDefault();
e.stopPropagation();
});
},
disable: function(){
this.element.data("disabled", true);
this.element.closest(".select").addClass("disabled");
},
enable: function(){
this.element.data("disabled", false);
this.element.closest(".select").removeClass("disabled");
},
toggleState: function(){
if (this.elem.disabled) {
this.disable();
} else {
this.enable();
}
},
reset: function(to_default){
var element = this.element;
var options = element.find("option");
var select = element.closest('.select');
var selected;
$.each(options, function(){
this.selected = !Utils.isNull(to_default) ? this.defaultSelected : false;
});
this.list.find("li").remove();
select.find(".select-input").html('');
this._createOptions();
selected = this.getSelected();
this._fireEvent("change", {
selected: selected
});
},
getSelected: function(){
var element = this.element;
var result = [];
element.find("option").each(function(){
if (this.selected) result.push(this.value);
});
return result;
},
val: function(val){
var that = this, element = this.element, o = this.options;
var input = element.siblings(".select-input");
var options = element.find("option");
var list_items = this.list.find("li");
var result = [];
var multiple = element.attr("multiple") !== undefined;
var option;
var i, html, list_item, option_value, selected, group;
if (Utils.isNull(val)) {
$.each(options, function(){
if (this.selected) result.push(this.value);
});
return multiple ? result : result[0];
}
$.each(options, function(){
this.selected = false;
});
list_items.removeClass("active").removeClass(o.clsOptionActive);
input.html('');
if (Array.isArray(val) === false) {
val = [val];
}
$.each(val, function(){
for (i = 0; i < options.length; i++) {
option = options[i];
html = Utils.isValue(option.getAttribute('data-template')) ? option.getAttribute('data-template').replace("$1", option.text) : option.text;
if (""+option.value === ""+this) {
option.selected = true;
break;
}
}
for(i = 0; i < list_items.length; i++) {
list_item = $(list_items[i]);
group = list_item.data("group");
option_value = list_item.attr("data-value");
if (""+option_value === ""+this) {
if (o.showGroupName && group) {
html += " <span class='selected-item__group-name'>" + group + "</span>";
}
if (multiple) {
list_item.addClass("d-none");
input.append(that._addTag(html, list_item));
// tag = $("<div>").addClass("tag").addClass(o.clsSelectedItem).html("<span class='title'>"+html+"</span>").appendTo(input);
// tag.data("option", list_item);
// $("<span>").addClass("remover").addClass(o.clsSelectedItemRemover).html("×").appendTo(tag);
} else {
list_item.addClass("active").addClass(o.clsOptionActive);
input.html(html);
}
break;
}
}
});
selected = this.getSelected();
this._fireEvent("change", {
selected: selected
});
},
options: function(op, selected, delimiter){
return this.data(op, selected, delimiter);
},
data: function(op, selected, delimiter){
var element = this.element;
var option_group, _selected;
var _delimiter = delimiter || ",";
if (typeof selected === "string") {
_selected = selected.toArray(_delimiter).map(function(v){
return isNaN(v) ? v : +v;
});
} else if (Array.isArray(selected)) {
_selected = selected.slice().map(function(v){
return isNaN(v) ? v : +v;
});
} else {
_selected = [];
}
element.empty();
if (typeof op === 'string') {
element.html(op);
} else if (Utils.isObject2(op)) {
$.each(op, function(key, val){
if (Utils.isObject2(val)) {
option_group = $("<optgroup label=''>").attr("label", key).appendTo(element);
$.each(val, function(key2, val2){
var op = $("<option>").attr("value", key2).text(val2).appendTo(option_group);
if (_selected.indexOf(+key2) > -1) {
op.prop("selected", true);
}
});
} else {
var op = $("<option>").attr("value", key).text(val).appendTo(element);
if (_selected.indexOf(key) > -1) {
op.prop("selected", true);
}
}
});
}
this._createOptions();
return this;
},
addOption: function(val, title, selected){
var element = this.element;
var option = $("<option>").attr("value", val).text(title ? title : val)
element.append(option)
if (selected) {
option.prop("selected", true)
}
this._createOptions();
return this;
},
addOptions: function(values){
var that = this;
if (!values) {
return this;
}
if (Array.isArray(values)) {
$.each(values, function(){
var o = this;
if (Metro.utils.isObject2(o)) {
that.addOption(o.val, o.title, o.selected)
} else {
that.addOption(o)
}
})
} else if (Metro.utils.isObject2(values)) {
$.each(values, function(key, val){
that.addOption(key, val);
})
}
return this;
},
removeOption: function(val){
var element = this.element;
var options = element.find("option")
options.each(function(){
var $el = $(this)
if ($el.attr("value") == val) {
$el.remove()
}
})
this._createOptions();
return this;
},
removeOptions: function(values){
var element = this.element;
var options = element.find("option")
if (!values || !Array.isArray(values)) {
return this;
}
options.each(function(){
var $el = $(this);
var val = $el.attr("value");
if (values.indexOf(val) > -1) {
$el.remove()
}
})
this._createOptions();
return this;
},
changeAttribute: function(attributeName){
if (attributeName === 'disabled') {
this.toggleState();
}
},
destroy: function(){
var element = this.element;
var container = element.closest(".select");
var drop_container = container.find(".drop-container");
var input = element.siblings(".select-input");
var filter_input = drop_container.find("input");
var list = drop_container.find("ul");
var clearButton = container.find(".input-clear-button");
container.off(Metro.events.click);
container.off(Metro.events.click, ".input-clear-button");
input.off(Metro.events.click);
filter_input.off(Metro.events.blur);
filter_input.off(Metro.events.focus);
list.off(Metro.events.click, "li");
filter_input.off(Metro.events.keyup);
drop_container.off(Metro.events.click);
clearButton.off(Metro.events.click);
drop_container.data("dropdown").destroy();
return element;
}
});
$(document).on(Metro.events.click, function(){
$(".select").removeClass("focused");
}, {ns: "blur-select-elements"});
}(Metro, m4q));