Search option in materialize form select
No option for creating Search bar inside select form control?
Can you explain more what feature you are describing? Add some images or code examples to illustrate your point.
It is something like under Live search in https://silviomoreto.github.io/bootstrap-select/examples/
Select2 FTW ! https://select2.github.io/
Add this to your CSS
.select2 .selection .select2-selection--single, .select2-container--default .select2-search--dropdown .select2-search__field {
border-width: 0 0 1px 0 !important;
border-radius: 0 !important;
height: 2.05rem;
}
.select2-container--default .select2-selection--multiple, .select2-container--default.select2-container--focus .select2-selection--multiple {
border-width: 0 0 1px 0 !important;
border-radius: 0 !important;
}
.select2-results__option {
color: #66bb6a;
padding: 8px 16px;
font-size: 16px;
}
.select2-container--default .select2-results__option--highlighted[aria-selected] {
background-color: #eee !important;
color: #66bb6a !important;
}
.select2-container--default .select2-results__option[aria-selected=true] {
background-color: #e1e1e1 !important;
}
.select2-dropdown {
border: none !important;
box-shadow: 0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);
}
.select2-container--default .select2-results__option[role=group] .select2-results__group {
background-color: #333333;
color: #fff;
}
.select2-container .select2-search--inline .select2-search__field {
margin-top: 0 !important;
}
.select2-container .select2-search--inline .select2-search__field:focus {
border-bottom: none !important;
box-shadow: none !important;
}
.select2-container .select2-selection--multiple {
min-height: 2.05rem !important;
}
.select2-container--default.select2-container--disabled .select2-selection--single {
background-color: #ddd !important;
color: rgba(0,0,0,0.26);
border-bottom: 1px dotted rgba(0,0,0,0.26);
}
IMO it would be awesome to have this feature 'out of the box' with Materialize. Until then @matrix818181 's solution seems to be viable. Otherwise, if you want to have the Material Design look and are willing to using AngularJS, I can also recommend angular-material's solution (https://material.angularjs.org/latest/demo/select - check the 'Select Header' section)
I have implemented the floating label for select2. Check this pen http://codepen.io/eskawl/pen/YqjQYO
Selectize.js seems to be better than select2. Here is the material design implementation for selectize. http://codepen.io/eskawl/pen/LNaGVG
Thanks for the reply My requirement is have a search bar at the top of select bar to search and select multiple options
On Sun, May 1, 2016 at 3:10 AM, Alan Chang [email protected] wrote:
Can you explain more what feature you are describing? Add some images or code examples to illustrate your point.
— You are receiving this because you authored the thread. Reply to this email directly or view it on GitHub https://github.com/Dogfalo/materialize/issues/3096#issuecomment-215996927
This would be really smth great to have! another option is to add smth like bootstrap typeahead(http://angular-ui.github.io/bootstrap/#/typeahead)
Maybe https://github.com/Dogfalo/materialize/pull/2669 does the trick for you
I needed that on my project and marked a change in the materializecss.js
I hope that help you guys JSFiddle:
Add it before add options in select:
//Added to search
var applySeachInList = function () {
var ul = $(this).closest('ul');
var searchValue = $(this).val();
var options = ul.find('li')
.find('span.filtrable');
options.each(function () {
if (typeof (this.text()) == 'string') {
var liValue = this.text().toLowerCase();
if (liValue.indexOf(searchValue.toLowerCase()) === -1) {
$(this).hide();
$(this).parent().hide();
} else {
$(this).show();
$(this).parent().show();
}
}
});
}
//Added to search
var setSearchableOption = function () {
var placeholder = $select.attr('searchable');
var element = $('<span><input type="text" class="search" style="margin: 5px 0px 16px 15px; width: 96%;" placeholder="' + placeholder + '"></span>');
options.append(element);
element.find('.search').keyup(applySeachInList);
}
//Added to search
var searchable = $select.attr('searchable') ? true : false;
//Added to search
if (searchable) {
setSearchableOption();
}
Then change $newSelect.on('blur') event to:
$newSelect.on('blur', function () {
if (!multiple && !searchable) {
$(this).trigger('close');
}
options.find('li.selected').removeClass('selected');
});
Below add this:
//Added to search
if (!multiple && searchable) {
options.find('li').on('click', function () {
$newSelect.trigger('close');
});
}
And in $(window).on() add this:
//Changed to search to treat search
$(window).on({
'click': function () {
(multiple || searchable) && (optionsHover || $newSelect.trigger('close'));
}
});
UPDATE: Fixed for firefox 👍
Not working for firefox.
I haven't tested in multiple browsers but here is an implementation I used with select2 https://codepen.io/CarlBoneri/pen/oYojBZ
Brunno's solution doesn't work in Firefox because of the outerText property, I've replaced it with JQuery's .text() or you can use innerText. I also had a problem of the dropdown closing so I added an on click listener to the input element and I stopped it from propagating. This solved the issue of the dropdown closing and the issue of the search not working in Firefox.
While there isn't a field to search in. You can already type to search for a particular option.
@Dogfalo Yeah, that's a nice feature but I think one of the main issues with it is that it isn't really very user friendly. The user can't see/ammend what they have typed for instance.
@Dogfalo while it helps some use cases, I don't think #4361 actually solves the problem. It could be a list of thousands that needs searching. What we ultimately need is a searchable dropdown with ajax data retrieval. If my javascript was good enough I would attempt it myself, but...
Hi, i have added search input before first li item in ul list
<ul><span class="col s12"><i class="material-icons prefix">search</i><input id="searchBoxListSelectID" class="searchBoxListSelect" placeholder="search" type="text"></span><li>option 1</li><li>option 2</li></ul>
but i cannot make stopPropagation in input event, so the list always closed/hidden after i clicked the input search.
i've been tried these event handler
$(document).on('click', ".searchBoxListSelect", function (e) { e.stopPropagation(); }); $(document).on('blur', ".searchBoxListSelect", function (e) { e.stopPropagation(); }); $(document).on('focus', ".searchBoxListSelect", function (e) { e.stopPropagation(); }); $(document).on('keydown', '.searchBoxListSelect', function (e) { e.stopPropagation(); }); $(document).on('keyup', '.searchBoxListSelect', function (e) { e.stopPropagation(); }); $(document).on('keypress', '.searchBoxListSelect', function (e) { e.stopPropagation() ; });
but the list still closed
@brunnodev
Thank you so much for your great solution for filtering options.
That didn't work somehow at v0.100.2 but I made some modifications to do work.
var applySeachInList = function () {
var ul = $(this).closest('ul');
var searchValue = $(this).val();
var options = ul.find('li')
.find('span.filtrable');
options.prevObject.each(function (index, element) { // the length of options were 0
if (typeof ($(this).text()) == 'string') { // this.text():this could't be found
var liValue = $(this).text().toLowerCase();
if (liValue.indexOf(searchValue.toLowerCase()) === -1) {
$(this).hide();
// remove below
// $(this).parent().hide();
} else {
$(this).show();
// remove below
// $(this).parent().show();
}
}
});
}
When I focused on the text field, the select box got closed so I changed like this.
var setSearchableOption = function () {
var placeholder = $select.attr('searchable');
var element = $('<span><input type="text" class="search" style="margin: 5px 0px 16px 15px; width: 96%;" placeholder="' + placeholder + '"></span>');
// add below 3 lines
$(element).on('click', function(){
return false;
});
options.append(element);
element.find('.search').keyup(applySeachInList);
}
Anyway, thank you so much for your help. I would happy to overcome this problem.
@brunnodev @kazukiyo923
tnx alot guys
you saved me.
It is not select though I had similar problem and solved using autocomplete. Here is the code. Obviously we need validate/sanitize and some mechanism to readonly the input text in onAutocomplete though I wish this one ring someone's bell.
I updated the hack to work with v1.0.0 I hop this will help someone. Working demo : https://codepen.io/yassinevic/pen/eXjqjb
add this bellow $(this.dropdownOptions).addClass('dropdown-content select-dropdown ' + (this.isMultiple ? 'multiple-select-dropdown' : ''));
// Create dropdown
this.$selectOptions = this.$el.children('option, optgroup');
this.dropdownOptions = document.createElement('ul');
this.dropdownOptions.id = "select-options-" + M.guid();
$(this.dropdownOptions).addClass('dropdown-content select-dropdown ' + (this.isMultiple ? 'multiple-select-dropdown' : ''));
//Added to search
var searchable = this.el.getAttribute('searchable') ? true : false;
if (searchable) {
this.options.dropdownOptions.autoFocus = false;
var placeholder = this.el.getAttribute('searchable');
var element = $('<span><input type="text" class="dropDownsearch" style="margin: 5px 0px 16px 15px; width: 96%;" placeholder="' + placeholder + '"></span>');
$(this.dropdownOptions).append(element);
element.on('keyup', function(event){
applySeachInList();
});
var applySeachInList = () => {
var searchVlaue= event.target.value.toLowerCase();
$(this.dropdownOptions).find('li').each((item) =>{
var current = $(item);
var liValue = current.text().toLowerCase();
if (liValue.indexOf(searchVlaue) === -1) {
current.css({ display: 'none' });
} else {
current.css({ display: 'block' });
}
});
this.dropdown.recalculateDimensions();
}
}
Change the line
if (this.options.closeOnClick && $target.closest('.dropdown-content').length && !this.isTouchMoving) {
with this one
if (this.options.closeOnClick && !$target.hasClass('dropDownsearch') && $target.closest('.dropdown-content').length && !this.isTouchMoving) {
yassinevic solution is the best. works like a charm
@yassinevic Hi, I did what you said, I'm using materializecss 1.0.0. Search bar is visible too. Problem 1: Textbox witdth is larger than the container, thus giving horizontal scrollbars, you've given 96%, i think. Problem 2: Search functionality is not working. Probably coz I am using with .NET's DropDown. However, I tried with Select too.
I checked your pen. But you have added options in jQuery too which would not be possible for me as I am generating content from server side using a db. Please help. I am stuck. Thanks
UDPATE
I tried an alert in applySearchInList. Blank alert(); appears but when I try to use alert(event.target.value.toLowerCase()) OR alert(event.target.value) OR alert(event.target) OR alert(event) but No output.
Ok... i checked and made some modifications: here's the answer:
element.on('keyup', function (event) { applySearchInList(event); }); var applySearchInList = (event) => {
Sorry, coming in a bit late, but found this issue when doing some research on how to do this.
As none of the tweaks provided here matched my taste, I made mine - based on @yassinevic's one.
Improvements :
- No need to modify Materialize JS files
- Grabs keyboard focus nicely
- No need for annoying JQuery
- Renders correctly for any widget width
Demo : https://jsfiddle.net/u1jrthwd/ [OUTDATED, check gist below]
The code is avalaible in this gist : https://gist.github.com/LeMinaw/52675dae35531999e22f670fb66d389b
To use it, just load this JS file and add searchable="placeholder" to your select element.
@LeMinaw Thank you so much your script helped me alot. How ever there is small issue with the script in mobile view. when selected to search it is taking to the first_name because on the new insert input you are setting the label for to "first_name".
Here is the link to the select file. Import this after importing Materialize js https://github.com/lkusam/MySite/blob/master/JS/MaterilizeSelect.js
The above file will also help with issue with the issue https://github.com/Dogfalo/materialize/issues/6464
I have modified the code to able to search properly. following is the code change for the select.js under the creating the dropdown
` //Added to search this.searchable = this.el.getAttribute('searchable') ? true : false; if (this.searchable) { this.options.dropdownOptions.autoFocus = false;
var placeholder = this.el.getAttribute('searchable');
var searchinput = this.el.getAttribute('serachname');
var element = $('<div class="input-field col m6"><input type="text" class="dropDownsearch" id="' + searchinput + '" style="margin: 5px 0px 16px 15px; width: 96%;"> <label for="' + searchinput + '">' + placeholder + '</label></div>');
$(this.dropdownOptions).append(element);
element.children().first().on('keyup', function (event)
{
applySeachInList(this.value);
});
var applySeachInList = (s) =>
{
var searchVlaue = s.toLowerCase();
$(this.dropdownOptions).find('li').each((item) =>
{
var current = $(item);
var liValue = current.text().toLowerCase();
if (liValue.indexOf(searchVlaue) === -1) {
current.css({ display: 'none' });
} else {
current.css({ display: 'block' });
}
});
this.dropdown.recalculateDimensions();
};
}
// End searchable`
Hi, I added some features to the script of @LeMinaw .
- nicer handling of optgroups -> hide if no child matches search pattern, show optgroup if it has at least 1 visible child
- handle keyboard navigation
- handle ArrowUp + ArrowDown focus selection
- grab focus when open event was triggered by keyboard
- supports multi select
- hooks and initialize with M.FormSelect.init
- sticky searchbar
Attention: one css rule has to be added:
.select-dropdown.dropdown-content li.hover { background-color: rgba(0,0,0,0.08); }
which is a copy of the materialize .select-dropdown.dropdown-content li:hover rule
You can find a demo here https://jsfiddle.net/cherrg/vgn3Law4/
*Edit: For latest changes look into the fiddle.
/*
* ====================================================================
* TLDR
* - add search bar to materialize formSelect
* - options line 41 - 44
* --------------------------------------------------------------------
* MaterializeCSS tweak to allow text filtering in multiple selects.
* - No need to modify Materialize JS files
* - Grabs keyboard focus nicely
* - No need for annoying JQuery
* - Renders correctly for any widget width
* - handles optgroups -> only hide empty optgroups,
* show optgroup if child is visible
* - handle keyboard navigation - ArrowUp + ArrowDown events
* - supports multi select
* - hooks M.FormSelect.init
* - add M.SelectSearch.init(els) for manual initialisation
* - do own autoload
* - could be disabled with option 'doOwnAutoload' (line 42)
* - sticky searchbar
* - disable by option 'stickySearchbar' (line 43)
* - scroll focused element into view (keyboard navigation)
* - disable by option 'scrollIntoView' (line 44)
* --------------------------------------------------------------------
* Installation
* - create js file, and load this after materialize.js
* - place attribute to your select:
* searchable="placeholder"
* - add css rule after materialize.css:
* .select-dropdown.dropdown-content li.hover {
* background-color: rgba(0,0,0,0.08); }
* --------------------------------------------------------------------
* Links
* - https://github.com/Dogfalo/materialize/issues/3096#issuecomment-778513315
* - https://jsfiddle.net/cherrg/vgn3Law4/
* ====================================================================
*/
(function(){
"use strict";
// script options ---------------------------------
const keyBoardOnMobile = true;
const doOwnAutoload = true;
const stickySearchbar = true;
const scrollIntoView = true;
// helper functions -------------------------------
const hasClass = function(el, className){
if (el.classList) return el.classList.contains(className);
return (new RegExp('( |^)' + className + '( |$)')).test(className);
};
const addClass = function(el, className){
if (el.classList) el.classList.add(className)
else if (!hasClass(el, className)) el.className += " " + className;
}
const removeClass = function(el, className){
if (el.classList) el.classList.remove(className)
else if (hasClass(el, className)) el.className = el.className.replace(new RegExp('( |^)' + className + '( |$)'), ' ');
}
const scrollIntoViewIfNeeded = function ( el, strict ) {
// --------------------
// init parameters
// unpack jquery objects
if (typeof jQuery === "function" && el instanceof jQuery) {
el = el[0];
}
if (typeof strict !== 'boolean') {
strict = true;
}
let parent = el.parentNode;
let parent_rect = parent.getBoundingClientRect();
let target_rect = el.getBoundingClientRect();
if (stickySearchbar) {
// as searchbar is first element, reduce parent height and top by search bar size
let searchbar_rect = parent.firstElementChild.getBoundingClientRect();
parent_rect = {
top: parent_rect.top + searchbar_rect.height,
height: parent_rect.height - searchbar_rect.height,
left: parent_rect.left,
width: parent_rect.width
};
}
// --------------------
// scroll into view
if (target_rect.top < parent_rect.top && (strict || target_rect.top + target_rect.height < parent_rect.top)){
// element out of bounds -> top
// as search bar is forst element
let diff = parent_rect.top - target_rect.top;
parent.scrollBy(0, -diff);
} else if (target_rect.top + target_rect.height > parent_rect.top + parent_rect.height && (strict || target_rect.top > parent_rect.top + parent_rect.height)) {
let diff = parent_rect.top + parent_rect.height - (target_rect.top + target_rect.height);
parent.scrollBy(0, -diff);
}
}
const init = function (els, force_init) {
if (typeof force_init !== "boolean") {
force_init = false;
}
if (typeof jQuery === "function" && els instanceof jQuery) {
els = els.toArray();
} else if (typeof els === 'object' && !els.hasOwnProperty('forEach') && typeof els.forEach !== 'function') {
els = [els];
}
if (typeof els === 'undefined'|| els === null) return;
els.forEach(elem => {
if (!force_init && !elem.hasAttribute('searchable')) {
// filter for searchable attribute
return;
}
if (elem.dataset.hasOwnProperty('searchableInitDone')) {
return;
} else {
elem.dataset.searchableInitDone = '1';
}
const select = elem.M_FormSelect;
const options = select.dropdownOptions.querySelectorAll('li');
// Add search box to dropdown
const placeholderText = select.el.getAttribute('searchable');
const searchBox = document.createElement('div');
searchBox.style.cssText =
'padding: 6px 16px 0 16px;' +
(stickySearchbar?
// search boy sticky on top
'position: -webkit-sticky;' +
'position: sticky;' +
'top: 0;' +
'z-index: 1;' +
'background-color: inherit;' : '');
searchBox.innerHTML = `<input type="text" placeholder="${placeholderText}">`;
select.dropdownOptions.prepend(searchBox);
const searchInput = searchBox.firstElementChild;
// Function to filter dropdown options
function filterOptions(event) {
// fix index value -> as materialize key handler changes value after keydown - event
if (event.code === 'ArrowDown' || event.code === 'ArrowUp'){
let hover_idx = -1;
// find current hover index
for(let i = 0; i < options.length; i++){
if (hover_idx === -1 && hasClass(options[i], 'hover')){
hover_idx = i;
break;
}
}
if (hover_idx > -1) {
select.dropdown.focusedIndex = hover_idx + 1;
if (scrollIntoView) {
scrollIntoViewIfNeeded(options[hover_idx], true);
}
}
return;
} else if (event.code === 'Enter') {
return;
}
// search for value
const searchText = searchInput.value.toLowerCase().trim();
let lastOptgroup = null;
for(let i = 0; i < options.length; i++){
const text = options[i].textContent.toLowerCase();
const display = text.indexOf(searchText) === -1 ? 'none' : 'block';
if (hasClass(options[i], 'optgroup')) {
lastOptgroup = display === 'none' ? options[i] : null;
} else if (!hasClass(options[i], 'optgroup-option')) {
lastOptgroup = null;
} else {
if (display !== 'none' && lastOptgroup !== null) {
lastOptgroup.style.display = display;
}
}
options[i].style.display = display;
}
select.dropdown.recalculateDimensions();
// recalculate may moves focus away in case of resize
searchInput.focus();
}
// Function to handle ArrowUp/ArrowDown keyboard events
// has to be hooked on keydown to get repeated key events in case of press&hold
function filterNavigate(event) {
// handle keyboard focus events
if (event.code === 'ArrowDown' || event.code === 'ArrowUp'){
let hover_idx = -1; // currently hovered option
let selected_idx = -1; // currently selected option - start point for up/down, used if nothing hovered
let visibles = [];
// find current hover index
for(let i = 0; i < options.length; i++){
let optionIsVisible = window.getComputedStyle(options[i]).display !== 'none';
if (hover_idx === -1 && hasClass(options[i], 'hover')){
hover_idx = i;
}
if(optionIsVisible){
visibles.push(i);
if (hover_idx === -1 && selected_idx === -1 && hasClass(options[i], 'selected')){
selected_idx = i;
}
}
}
if (hover_idx === -1 && selected_idx !== -1) {
hover_idx = selected_idx;
}
let new_hover_idx = -1;
if (visibles.length > 0){
let direction = event.code === 'ArrowDown' ? 1 : -1;
if (hover_idx === -1 || visibles.indexOf(hover_idx) === -1) {
new_hover_idx = visibles[direction === 1 ? 0 : visibles.length - 1];
} else {
new_hover_idx = visibles[(visibles.length + visibles.indexOf(hover_idx) + direction) % visibles.length];
}
}
// unset hover
if (hover_idx > -1) {
removeClass(options[hover_idx], 'hover');
}
if (new_hover_idx > -1) {
addClass(options[new_hover_idx], 'hover');
select.dropdown.focusedIndex = new_hover_idx + 1;
}
}
}
// Function to give keyboard focus to the search input field
function focusSearchBox() {
searchInput.focus({
preventScroll: true
});
}
// Function to init search field and pull focus to search input
function onSelectDropdownShow() {
// clear search on open
if (searchInput.value!=='') {
searchInput.value='';
// show all entries
setTimeout(function(){
searchBox.dispatchEvent(new KeyboardEvent('keyup', {'key':'Backspace', 'code':'Backspace', 'keyCode':8}));
}, 200);
}
focusSearchBox();
}
select.dropdown.options.autoFocus = false;
if (keyBoardOnMobile || window.matchMedia('(hover: hover) and (pointer: fine)').matches) {
// listen to Click and ArrowDown select open events
select.input.addEventListener('click', onSelectDropdownShow);
select.input.addEventListener('keyup', function(event){
if (event.code==='ArrowDown') { // select open event
onSelectDropdownShow();
}
});
for(let i = 0; i < options.length; i++){
options[i].addEventListener('click', focusSearchBox);
}
}
// filter text
searchBox.addEventListener('keyup', filterOptions);
// handle ArrowUp/ArrowDown keyboard events
searchBox.addEventListener('keydown', filterNavigate);
});
}
// hook materialize select init
if (typeof M !== 'object') {
let script_name = (typeof document.currentScript != 'undefined') ? document.currentScript.getAttribute('src') : 'materializecss-select-search.js';
console.error('['+script_name+'] Has to be loaded AFTER materialize.js and before M.AutoInit() call. To manually init: call M.SelectSearch.init(els);');
} else {
M['SelectSearch'] = {
'init' : function(els) {
init(els, true);
}
};
const SelectInit = M.FormSelect.init;
M.FormSelect.init = function (els, options) {
let t = this;
SelectInit.call(t, els, options);
init(els, false);
}
}
// auto init disabled
if (doOwnAutoload) {
document.addEventListener('DOMContentLoaded', () => {
init(document.querySelectorAll('select[searchable]'), true);
});
}
})();
Hi, Nice job. What about $('select').formSelect(); ? It doesn't seem to work anymore after changing the value via $('# y').Val('60');