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

New: Tag component #1798

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,11 @@ function setInitFlag(element) {
* @param {string} selector - Selector to search for in the document.
* @param {Function} Constructor - A constructor function.
* @param {HTMLElement} [scope] - A dom node in which to query the selector.
* @param {object} config - Configuration will be provided to the Constructor's init()
* If not supplied, it defaults to the `document`.
* @returns {Array} List of instances that were instantiated.
*/
function instantiateAll(selector, Constructor, scope) {
function instantiateAll(selector, Constructor, scope, config = {}) {
const base = scope || document;
const elements = base.querySelectorAll(selector);
const insts = [];
Expand All @@ -115,7 +116,7 @@ function instantiateAll(selector, Constructor, scope) {
element = elements[i];
if (contains(element, INIT_FLAG) === false) {
inst = new Constructor(element);
inst.init();
inst.init(config);
insts.push(inst);
}
}
Expand Down
45 changes: 37 additions & 8 deletions packages/cfpb-forms/src/organisms/Multiselect.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
isMobileUserAgent,
instantiateAll,
} from '@cfpb/cfpb-atomic-component';
import MultiselectModel from './MultiselectModel.js';
import MultiselectModel, { MAX_SELECTIONS } from './MultiselectModel.js';
import { create } from './MultiselectUtils.js';

import * as closeIconSrc from '@cfpb/cfpb-icons/src/icons/error.svg';
Expand All @@ -27,6 +27,12 @@ const KEY_UP = 'ArrowUp';
const KEY_DOWN = 'ArrowDown';
const KEY_TAB = 'Tab';

// Configuration default
const DEFAULT_CONFIG = {
renderTags: true, // Allow the Multiselect to generate the Tag elements in the DOM
maxSelections: MAX_SELECTIONS, // Maximum number of options a user can select
};

/**
* Multiselect
* @class
Expand All @@ -49,6 +55,7 @@ function Multiselect(element) {
let _placeholder;
let _model;
let _options;
let _config; // Multiselect configuration object

// Markup elems, convert this to templating engine in the future.
let _containerDom;
Expand Down Expand Up @@ -285,10 +292,13 @@ function Multiselect(element) {
const dataOptionSel = '[data-option="' + option.value + '"]';
const _selectionsItemDom = _selectionsDom.querySelector(dataOptionSel);

if (typeof _selectionsItemDom !== 'undefined') {
_selectionsDom.removeChild(_selectionsItemDom);
// If the <Tag> exists
if (typeof _selectionsItemDom !== 'undefined' && _selectionsItemDom) {
_selectionsDom?.removeChild(_selectionsItemDom);
}
} else {
}
// Else, if we are configured to display <Tag>s then render them
else if (_config?.renderTags && _selectionsDom) {
_createSelectedItem(_selectionsDom, option);
}
_model.toggleOption(optionIndex);
Expand Down Expand Up @@ -512,7 +522,8 @@ function Multiselect(element) {

_optionItemDoms.push(optionsItemDom);

if (isChecked) {
// Create <Tag> if enabled
if (isChecked && _config?.renderTags) {
_createSelectedItem(_selectionsDom, option);
}
}
Expand All @@ -527,9 +538,10 @@ function Multiselect(element) {

/**
* Set up and create the multiselect.
* @param {object} multiselectConfig - Multiselect configuration options
* @returns {Multiselect} An instance.
*/
function init() {
function init(multiselectConfig = DEFAULT_CONFIG) {
if (!setInitFlag(_dom)) {
return this;
}
Expand All @@ -543,8 +555,12 @@ function Multiselect(element) {
_placeholder = _dom.getAttribute('placeholder');
_options = _dom.options || [];

// Allow devs to pass the config settings they want and not worry about the rest
_config = { ...DEFAULT_CONFIG, ...multiselectConfig };

if (_options.length > 0) {
_model = new MultiselectModel(_options, _name).init();
// Store underlying model so we can expose it externally
_model = new MultiselectModel(_options, _name, _config).init();
const newDom = _populateMarkup();

/* Removes <select> element,
Expand All @@ -562,6 +578,14 @@ function Multiselect(element) {
return this;
}

/**
* Allow external access to the underlying model for integration/customization when used in other applications.
* @returns {object} Model
*/
function getModel() {
return _model;
}

// Attach public events.
this.init = init;
this.expand = expand;
Expand All @@ -571,11 +595,16 @@ function Multiselect(element) {
this.addEventListener = eventObserver.addEventListener;
this.removeEventListener = eventObserver.removeEventListener;
this.dispatchEvent = eventObserver.dispatchEvent;
this.getModel = getModel;
this.updateSelections = _updateSelections;
this.selectionClickHandler = _selectionClickHandler;
this.selectionKeyDownHandler = _selectionKeyDownHandler;

return this;
}

Multiselect.BASE_CLASS = BASE_CLASS;
Multiselect.init = () => instantiateAll(`.${BASE_CLASS}`, Multiselect);
Multiselect.init = (config) =>
instantiateAll(`.${BASE_CLASS}`, Multiselect, undefined, config);

export { Multiselect };
14 changes: 7 additions & 7 deletions packages/cfpb-forms/src/organisms/MultiselectModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
let UNDEFINED;

// How many options may be checked.
const MAX_SELECTIONS = 5;
export const MAX_SELECTIONS = 5;

/**
* Escapes a string.
Expand All @@ -29,10 +29,13 @@ function stringMatch(x, y) {
* @param {HTMLOptionsCollection} options -
* Set of options from a <select> element.
* @param {string} name - a unique name for this multiselect.
* @param {object} config - Customization of Multiselect behavior
*/
function MultiselectModel(options, name) {
function MultiselectModel(options, name, config) {
const _options = options;
const _name = name;
const _max = config?.maxSelections || MAX_SELECTIONS;

let _optionsData = [];

let _selectedIndices = [];
Expand All @@ -59,7 +62,7 @@ function MultiselectModel(options, name) {
* True if the maximum number of options are checked, false otherwise.
*/
function isAtMaxSelections() {
return _selectedIndices.length === MAX_SELECTIONS;
return _selectedIndices.length >= _max;
}

/**
Expand Down Expand Up @@ -108,10 +111,7 @@ function MultiselectModel(options, name) {
function toggleOption(index) {
_optionsData[index].checked = !_optionsData[index].checked;

if (
_selectedIndices.length < MAX_SELECTIONS &&
_optionsData[index].checked
) {
if (_selectedIndices.length < _max && _optionsData[index].checked) {
_selectedIndices.push(index);
_selectedIndices.sort();

Expand Down
2 changes: 1 addition & 1 deletion packages/cfpb-forms/src/organisms/multiselect.less
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ select.o-multiselect {
pointer-events: none;

&::after {
content: 'Reached maximum of five selections';
content: 'Reached maximum number of selections';
}
}

Expand Down