Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to select multiple options #57

Open
wants to merge 15 commits into
base: staging
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions dev/html/multiple-select.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<section>
<h2 class="heading-separator"><span>Multiple select</span></h2>

<label class="row">
<strong>Allow to select multiple options</strong>
<span class="col-right">
<select data-easy-select='{"search": "true", "multiple": "true", "align":"right"}'>
<option value="1">One</option>
<option value="2">Two - Hai</option>
<option value="3">Three - 第三的</option>
<option value="4">Four - よん</option>
<option value="5">Five - これは五です。</option>
<option value="6">Six</option>
<option value="7">Seven</option>
<option value="8">Eight</option>
<option value="9">Nine</option>
<option value="10">Ten</option>
</select>
</span>
</label>
</section>
8 changes: 8 additions & 0 deletions dev/js/test-multiple-select.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import html from "../html/multiple-select.html";

export function testMultipleSelect(root){
root.insertAdjacentHTML('beforeend', html);

// Init: default layout
EasySelect.init();
}
4 changes: 3 additions & 1 deletion dev/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {testLayout} from "./js/test-layout";
import {testMethods} from "./js/test-methods";
import {testDisabled} from "./js/test-disabled";
import {testSearch} from "./js/test-search";
import {testMultipleSelect} from "./js/test-multiple-select";

// import package info
const packageInfo = require('../package.json');
Expand All @@ -34,4 +35,5 @@ testMethods(root);
testSearch(root);
testLayout(root);
testInit(root);
testDisabled(root);
testDisabled(root);
testMultipleSelect(root);
3 changes: 2 additions & 1 deletion dev/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ section {
}

.col-right {
width: 200px;
width: 400px;
display: block;
text-align: right;
}

code {
Expand Down
82 changes: 64 additions & 18 deletions src/_index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {getSelectData, val} from "./data";
import {fireOnChangeEvent, init} from "./methods";
import {getOptionHTML, updateDropdownHTML} from "./layout";
import {findObjectInArray, getSelectTag} from "./utils";
import {getCurrentInnerHTML, getOptionHTML, updateDropdownHTML} from "./layout";
import {findObjectInArray, getOptionByValue, getSelectTag} from "./utils";
import {EventsManager, getOptionsFromAttribute} from "@phucbm/os-util";
import {CLASSES, ATTRS, DEFAULTS} from './configs'

Expand Down Expand Up @@ -114,7 +114,7 @@ class EasySelect{
* @param disabled
*/
disableOption(optionValue, disabled){
const option = this.selectTag.querySelector(`option[value="${optionValue}"]`);
const option = getOptionByValue(this, optionValue);

if(!option){
console.warn(`Option with value "${optionValue}" is not found.`);
Expand Down Expand Up @@ -144,7 +144,7 @@ class EasySelect{

if(this.selectTagData.length){
// update current
this.current.innerHTML = getOptionHTML(this);
this.current.innerHTML = getCurrentInnerHTML(this);

// if not native select
if(!this.options.nativeSelect){
Expand Down Expand Up @@ -183,18 +183,52 @@ class EasySelect{
select(value){
if(this.isDisabled) return;

// skip duplicate value
if(value === val(this)) return;
// todo: create isSelectedOption()
const isSelected = val(this, 'array').includes(value);

// value exists in data object => update value
if(typeof findObjectInArray(this.selectTagData, 'value', value) !== 'undefined'){
this.selectTag.value = value;
fireOnChangeEvent(this);
// treat selected option
if(isSelected){
if(this.options.multiple) this.deselect(value);
return;
}

// warning
if(this.options.warning) console.warn(`Option[value="${value}"] is not found in this select!`);

// value not exists in data object => update value
if(typeof findObjectInArray(this.selectTagData, 'value', value) === 'undefined'){
// warning
if(this.options.warning) console.warn(`Option[value="${value}"] is not found in this select!`);
return;
}


// set selected value to select tag (single select only)
// with multi select, update select tag will lead to missing previous selected values
if(!this.options.multiple) this.selectTag.value = value;

// make the option selected
const option = getOptionByValue(this, value);
option.selected = true;

fireOnChangeEvent(this);
}


/**
* Deselect by value
* @param value
*/
deselect(value){
// todo: bug multi deselect
if(this.isDisabled) return;

// get option
const option = getOptionByValue(this, value);
if(!option) return;

// deselect
option.selected = false;

fireOnChangeEvent(this);
}

/**
Expand Down Expand Up @@ -222,19 +256,31 @@ class EasySelect{
if(this.isDisabled) return;

// update current HTML
this.current.innerHTML = getOptionHTML(this);
this.current.innerHTML = getCurrentInnerHTML(this);
const newValue = val(this);
const newValueArray = val(this, 'array');

/** Dropdown **/
if(!this.options.nativeSelect){
// active option
this.dropdown.querySelectorAll(`[${ATTRS.optionAttr}]`).forEach(item => {
item.classList.remove(CLASSES.active);
// todo: this.selectTagData not updated
//console.log(this.selectTagData)

// update active class
this.selectTagData.forEach(option => {
const isSelected = newValueArray.includes(option.value);
if(isSelected){
// activate selected values
// todo: save dropdown el to selectTagData
this.dropdown.querySelector(`[${ATTRS.optionAttr}="${option.value}"]`).classList.add(CLASSES.active);
}else{
this.dropdown.querySelector(`[${ATTRS.optionAttr}="${option.value}"]`).classList.remove(CLASSES.active);
}
});
this.dropdown.querySelector(`[${ATTRS.optionAttr}="${newValue}"]`).classList.add(CLASSES.active);

// close on change
if(this.options.closeOnChange) this.close();
let isCloseOnChange = this.options.closeOnChange;
if(this.options.multiple) isCloseOnChange = false; // not close in multi select
if(isCloseOnChange) this.close();
}

// update value attribute
Expand Down
45 changes: 45 additions & 0 deletions src/_style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -274,3 +274,48 @@
min-height: var(--es-height);
padding: 5px 20px;
}


/****************************
* Checkbox
****************************/
.easy-select.es-multi-select {
.es-current-label {
max-width: var(--es-dropdown-width);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

.es-dropdown {
.es-option {
position: relative;
padding-left: 45px;

&:not(.es-active) {
.es-checkbox:before {
opacity: 0;
}
}
}

.es-checkbox {
position: absolute;
top: .65em;
left: 20px;
width: 18px;
aspect-ratio: 1;
background-color: #e0e7ee;
border-radius: 3px;

&:before {
content: '✓';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: .7em;
}
}
}
}
6 changes: 6 additions & 0 deletions src/configs.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export const CLASSES = {
searchEnabled: 'es-search-enabled',
searchWrapper: 'es-search-wrapper',
searchEmpty: 'es-search-empty',

// multi select
multipleSelect: 'es-multi-select'
}
/**
* Attributes
Expand All @@ -39,6 +42,9 @@ export const DEFAULTS = {
closeOnChange: true,
align: "left",

multiple: false,
multipleLabel: "Select multiple options",

// show search input inside dropdown
search: false,
emptySearchText: "There are no options", // optional, text appear when search empty
Expand Down
38 changes: 30 additions & 8 deletions src/data.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,35 @@
import {getIndex, getSelectedOption, stringToSlug} from "./utils";
import {getIndex, getMultipleSelectedValues, getSelectedOption, stringToSlug} from "./utils";

/****************************************************
********************** Data *********************
***************************************************/

/**
* Get value
* @param context
* @param type
* @returns {*}
*/
export function val(context){
context.value = context.selectTag.value;
export function val(context, type = 'string'){
const valueArray = getMultipleSelectedValues(context.selectTag);
let value;

switch(type){
case "array":
value = valueArray;
break;
default:
// string
if(valueArray.length === 1){
value = valueArray[0]; // => "value"
}else if(valueArray.length > 1){
value = valueArray.join(','); // => "value1,value2"
}else{
value = ''; // => ""
}
}

context.value = value;
return context.value;
}

Expand All @@ -27,21 +47,23 @@ export function getSelectData(context){

/**
* Get option data
* @returns {{isSelected: boolean, index: *, id: string, label: *, value: (*|string|number|string[])}}
* @param context
* @param option
* @returns {{el: *, isSelected: *, index: *, id: string, label: string, isDisabled: (*|(() => boolean)|((setting: string) => boolean)|string|boolean), value: *}}
*/
export function getOptionData(context, option = undefined){
if(typeof option === 'undefined'){
// return selected option
option = getSelectedOption(context.selectTag);
}

const label = option.innerText;
const value = option.value;
const label = option?.innerText;
const value = option?.value;
const index = getIndex(option);
const id = stringToSlug(value) + '-' + index;
const isSelected = value === val(context);
const isSelected = val(context, 'array').includes(value); // tested with multi select
const el = option;
const isDisabled = option.disabled;
const isDisabled = option?.disabled;

return {id, label, value, isSelected, isDisabled, index, el};
}
Loading