diff --git a/src/scripts/src/choices.js b/src/scripts/src/choices.js index 563720c..c6913b9 100644 --- a/src/scripts/src/choices.js +++ b/src/scripts/src/choices.js @@ -80,9 +80,16 @@ class Choices { this.isValidElementType = this.isTextElement || this.isSelectElement; if (this.isTextElement) { - this.passedElement = new WrappedInput(this, passedElement, this.config.classNames); + this.passedElement = new WrappedInput({ + element: passedElement, + classNames: this.config.classNames, + delimiter: this.config.delimiter, + }); } else if (this.isSelectElement) { - this.passedElement = new WrappedSelect(this, passedElement, this.config.classNames); + this.passedElement = new WrappedSelect({ + element: passedElement, + classNames: this.config.classNames, + }); } if (!this.passedElement) { @@ -106,9 +113,9 @@ class Choices { this.highlightPosition = 0; this.canSearch = this.config.searchEnabled; - this.placeholder = false; + this.placeholderValue = false; if (!this.isSelectOneElement) { - this.placeholder = this.config.placeholder ? + this.placeholderValue = this.config.placeholder ? (this.config.placeholderValue || this.passedElement.element.getAttribute('placeholder')) : false; } @@ -1085,9 +1092,9 @@ class Choices { this.containerOuter.removeLoadingState(); if (this.isSelectOneElement) { - placeholderItem.innerHTML = (this.placeholder || ''); + placeholderItem.innerHTML = (this.placeholderValue || ''); } else { - this.input.placeholder = (this.placeholder || ''); + this.input.placeholder = (this.placeholderValue || ''); } } } @@ -2166,12 +2173,39 @@ class Choices { const input = this._getTemplate('input'); const dropdown = this._getTemplate('dropdown'); - this.containerOuter = new Container(this, containerOuter, this.config.classNames); - this.containerInner = new Container(this, containerInner, this.config.classNames); - this.input = new Input(this, input, this.config.classNames); - this.choiceList = new List(this, choiceList, this.config.classNames); - this.itemList = new List(this, itemList, this.config.classNames); - this.dropdown = new Dropdown(this, dropdown, this.config.classNames); + this.containerOuter = new Container({ + element: containerOuter, + classNames: this.config.classNames, + type: this.passedElement.element.type, + position: this.config.position, + }); + + this.containerInner = new Container({ + element: containerInner, + classNames: this.config.classNames, + type: this.passedElement.element.type, + position: this.config.position, + }); + + this.input = new Input({ + element: input, + classNames: this.config.classNames, + type: this.passedElement.element.type, + }); + + this.choiceList = new List({ + element: choiceList, + }); + + this.itemList = new List({ + element: itemList, + }); + + this.dropdown = new Dropdown({ + element: dropdown, + classNames: this.config.classNames, + type: this.passedElement.element.type, + }); } /** @@ -2189,8 +2223,8 @@ class Choices { if (this.isSelectOneElement) { this.input.placeholder = (this.config.searchPlaceholderValue || ''); - } else if (this.placeholder) { - this.input.placeholder = this.placeholder; + } else if (this.placeholderValue) { + this.input.placeholder = this.placeholderValue; this.input.setWidth(true); } diff --git a/src/scripts/src/components/container.js b/src/scripts/src/components/container.js index c7bf4f5..880a5a0 100644 --- a/src/scripts/src/components/container.js +++ b/src/scripts/src/components/container.js @@ -1,16 +1,15 @@ import { getWindowHeight, wrap } from '../lib/utils'; export default class Container { - constructor(instance, element, classNames) { - this.parentInstance = instance; - this.element = element; - this.classNames = classNames; - this.config = instance.config; + constructor({ element, type, classNames, position }) { + Object.assign(this, { element, classNames, type, position }); + this.isOpen = false; this.isFlipped = false; this.isFocussed = false; this.isDisabled = false; this.isLoading = false; + this.onFocus = this.onFocus.bind(this); this.onBlur = this.onBlur.bind(this); } @@ -57,16 +56,16 @@ export default class Container { if (dropdownPos === undefined) { return false; } + // If flip is enabled and the dropdown bottom position is // greater than the window height flip the dropdown. let shouldFlip = false; - if (this.config.position === 'auto') { + if (this.position === 'auto') { shouldFlip = dropdownPos >= windowHeight; - } else if (this.config.position === 'top') { + } else if (this.position === 'top') { shouldFlip = true; } - return shouldFlip; } @@ -129,7 +128,7 @@ export default class Container { enable() { this.element.classList.remove(this.classNames.disabledState); this.element.removeAttribute('aria-disabled'); - if (this.parentInstance.isSelectOneElement) { + if (this.type === 'select-one') { this.element.setAttribute('tabindex', '0'); } this.isDisabled = false; @@ -141,7 +140,7 @@ export default class Container { disable() { this.element.classList.add(this.classNames.disabledState); this.element.setAttribute('aria-disabled', 'true'); - if (this.parentInstance.isSelectOneElement) { + if (this.type === 'select-one') { this.element.setAttribute('tabindex', '-1'); } this.isDisabled = true; diff --git a/src/scripts/src/components/container.test.js b/src/scripts/src/components/container.test.js index 2fe90e5..e1d3df5 100644 --- a/src/scripts/src/components/container.test.js +++ b/src/scripts/src/components/container.test.js @@ -1,23 +1,23 @@ import { expect } from 'chai'; import { stub } from 'sinon'; import Container from './container'; -import { DEFAULT_CLASSNAMES, DEFAULT_CONFIG } from '../constants'; +import { DEFAULT_CLASSNAMES } from '../constants'; describe('components/container', () => { let instance; - let choicesInstance; let element; beforeEach(() => { - choicesInstance = { - config: { - ...DEFAULT_CONFIG, - }, - }; element = document.createElement('div'); element.id = 'container'; + document.body.appendChild(element); - instance = new Container(choicesInstance, document.getElementById('container'), DEFAULT_CLASSNAMES); + instance = new Container({ + element: document.getElementById('container'), + classNames: DEFAULT_CLASSNAMES, + position: 'auto', + type: 'text', + }); }); afterEach(() => { @@ -27,10 +27,6 @@ describe('components/container', () => { }); describe('constructor', () => { - it('assigns choices instance to class', () => { - expect(instance.parentInstance).to.eql(choicesInstance); - }); - it('assigns choices element to class', () => { expect(instance.element).to.eql(element); }); @@ -104,7 +100,7 @@ describe('components/container', () => { describe('passing dropdownPos', () => { describe('position config option set to "auto"', () => { beforeEach(() => { - instance.config.position = 'auto'; + instance.position = 'auto'; }); describe('dropdownPos is greater than window height', () => { @@ -122,7 +118,7 @@ describe('components/container', () => { describe('position config option set to "top"', () => { beforeEach(() => { - instance.config.position = 'top'; + instance.position = 'top'; }); it('returns true', () => { @@ -132,7 +128,7 @@ describe('components/container', () => { describe('position config option set to "bottom"', () => { beforeEach(() => { - instance.config.position = 'bottom'; + instance.position = 'bottom'; }); it('returns false', () => { @@ -310,7 +306,7 @@ describe('components/container', () => { describe('select one element', () => { beforeEach(() => { - instance.parentInstance.isSelectOneElement = true; + instance.type = 'select-one'; instance.enable(); }); @@ -344,7 +340,7 @@ describe('components/container', () => { describe('select one element', () => { beforeEach(() => { - instance.parentInstance.isSelectOneElement = true; + instance.type = 'select-one'; instance.disable(); }); diff --git a/src/scripts/src/components/dropdown.js b/src/scripts/src/components/dropdown.js index f2535ba..0936ed0 100644 --- a/src/scripts/src/components/dropdown.js +++ b/src/scripts/src/components/dropdown.js @@ -1,10 +1,7 @@ export default class Dropdown { - constructor(instance, element, classNames) { - this.parentInstance = instance; - this.element = element; - this.classNames = classNames; - this.dimensions = null; - this.position = null; + constructor({ element, type, classNames }) { + Object.assign(this, { element, type, classNames }); + this.isActive = false; } @@ -36,7 +33,7 @@ export default class Dropdown { this.element.classList.add(this.classNames.activeState); this.element.setAttribute('aria-expanded', 'true'); this.isActive = true; - return this.parentInstance; + return this; } /** @@ -48,6 +45,6 @@ export default class Dropdown { this.element.classList.remove(this.classNames.activeState); this.element.setAttribute('aria-expanded', 'false'); this.isActive = false; - return this.parentInstance; + return this; } } diff --git a/src/scripts/src/components/dropdown.test.js b/src/scripts/src/components/dropdown.test.js index 7aa8e72..770f0de 100644 --- a/src/scripts/src/components/dropdown.test.js +++ b/src/scripts/src/components/dropdown.test.js @@ -1,23 +1,20 @@ import { expect } from 'chai'; import sinon from 'sinon'; import Dropdown from './dropdown'; -import { DEFAULT_CLASSNAMES, DEFAULT_CONFIG } from '../constants'; +import { DEFAULT_CLASSNAMES } from '../constants'; describe('components/dropdown', () => { let instance; - let choicesInstance; let choicesElement; beforeEach(() => { - choicesInstance = { - config: { - ...DEFAULT_CONFIG, - }, - }; - choicesElement = document.createElement('div'); document.body.appendChild(choicesElement); - instance = new Dropdown(choicesInstance, choicesElement, DEFAULT_CLASSNAMES); + instance = new Dropdown({ + element: choicesElement, + type: 'text', + classNames: DEFAULT_CLASSNAMES, + }); }); afterEach(() => { @@ -26,10 +23,6 @@ describe('components/dropdown', () => { }); describe('constructor', () => { - it('assigns choices instance to instance', () => { - expect(instance.parentInstance).to.eql(choicesInstance); - }); - it('assigns choices element to instance', () => { expect(instance.element).to.eql(choicesElement); }); @@ -125,8 +118,8 @@ describe('components/dropdown', () => { expect(instance.isActive).to.equal(true); }); - it('returns parent instance', () => { - expect(actualResponse).to.eql(choicesInstance); + it('returns instance', () => { + expect(actualResponse).to.eql(instance); }); }); @@ -153,8 +146,8 @@ describe('components/dropdown', () => { expect(instance.isActive).to.equal(false); }); - it('returns parent instance', () => { - expect(actualResponse).to.eql(choicesInstance); + it('returns instance', () => { + expect(actualResponse).to.eql(instance); }); }); }); diff --git a/src/scripts/src/components/input.js b/src/scripts/src/components/input.js index b8b39ee..4e98599 100644 --- a/src/scripts/src/components/input.js +++ b/src/scripts/src/components/input.js @@ -1,8 +1,9 @@ import { calcWidthOfInput } from '../lib/utils'; export default class Input { - constructor(instance, element, classNames) { - this.parentInstance = instance; + constructor({ element, type, classNames, placeholderValue }) { + Object.assign(this, { element, type, classNames, placeholderValue }); + this.element = element; this.classNames = classNames; this.isFocussed = this.element === document.activeElement; @@ -47,7 +48,7 @@ export default class Input { * @private */ onInput() { - if (!this.parentInstance.isSelectOneElement) { + if (this.type !== 'select-one') { this.setWidth(); } } @@ -60,21 +61,15 @@ export default class Input { */ onPaste(e) { // Disable pasting into the input if option has been set - if (e.target === this.element && !this.parentInstance.config.paste) { + if (e.target === this.element && this.preventPaste) { e.preventDefault(); } } - /** - * Set focussed state - */ onFocus() { this.isFocussed = true; } - /** - * Remove focussed state - */ onBlur() { this.isFocussed = false; } @@ -115,7 +110,7 @@ export default class Input { this.setWidth(); } - return this.parentInstance; + return this; } /** @@ -124,12 +119,12 @@ export default class Input { * @return */ setWidth(enforceWidth) { - if (this.parentInstance.placeholder) { + if (this.placeholderValue) { // If there is a placeholder, we only want to set the width of the input when it is a greater // length than 75% of the placeholder. This stops the input jumping around. if ( (this.element.value && - this.element.value.length >= (this.parentInstance.placeholder.length / 1.25)) || + this.element.value.length >= (this.placeholderValue.length / 1.25)) || enforceWidth ) { this.element.style.width = this.calcWidth(); diff --git a/src/scripts/src/components/input.test.js b/src/scripts/src/components/input.test.js index 907c15a..e5957c9 100644 --- a/src/scripts/src/components/input.test.js +++ b/src/scripts/src/components/input.test.js @@ -1,21 +1,21 @@ import { expect } from 'chai'; import { stub } from 'sinon'; import Input from './input'; -import { DEFAULT_CLASSNAMES, DEFAULT_CONFIG } from '../constants'; +import { DEFAULT_CLASSNAMES } from '../constants'; describe('components/input', () => { let instance; - let choicesInstance; let choicesElement; beforeEach(() => { - choicesInstance = { - config: { - ...DEFAULT_CONFIG, - }, - }; choicesElement = document.createElement('input'); - instance = new Input(choicesInstance, choicesElement, DEFAULT_CLASSNAMES); + instance = new Input({ + element: choicesElement, + type: 'text', + classNames: DEFAULT_CLASSNAMES, + placeholderValue: null, + preventPaste: false, + }); }); afterEach(() => { @@ -24,10 +24,6 @@ describe('components/input', () => { }); describe('constructor', () => { - it('assigns choices instance to class', () => { - expect(instance.parentInstance).to.eql(choicesInstance); - }); - it('assigns choices element to class', () => { expect(instance.element).to.eql(choicesElement); }); @@ -93,7 +89,7 @@ describe('components/input', () => { describe('when element is select one', () => { it('does not set input width', () => { - instance.parentInstance.isSelectOneElement = true; + instance.type = 'select-one'; instance.onInput(); expect(setWidthStub.callCount).to.equal(0); }); @@ -101,7 +97,7 @@ describe('components/input', () => { describe('when element is not a select one', () => { it('sets input width', () => { - instance.parentInstance.isSelectOneElement = false; + instance.type = 'text'; instance.onInput(); expect(setWidthStub.callCount).to.equal(1); }); @@ -120,7 +116,7 @@ describe('components/input', () => { describe('when pasting is disabled and target is the element', () => { it('prevents default pasting behaviour', () => { - instance.parentInstance.config.paste = false; + instance.preventPaste = true; instance.onPaste(eventMock); expect(eventMock.preventDefault.callCount).to.equal(1); }); @@ -128,7 +124,7 @@ describe('components/input', () => { describe('when pasting is enabled', () => { it('does not prevent default pasting behaviour', () => { - instance.parentInstance.config.paste = true; + instance.preventPaste = false; instance.onPaste(eventMock); expect(eventMock.preventDefault.callCount).to.equal(0); }); @@ -264,10 +260,9 @@ describe('components/input', () => { expect(setWidthStub.callCount).to.equal(1); }); - it('returns parent instance', () => { - const actualResponse = instance.clear(); - const expectedResponse = choicesInstance; - expect(actualResponse).to.eql(expectedResponse); + it('returns instance', () => { + const response = instance.clear(); + expect(response).to.eql(instance); }); }); @@ -286,7 +281,7 @@ describe('components/input', () => { describe('with a placeholder', () => { describe('when value length is greater or equal to 75% of the placeholder length', () => { it('sets the width of the element based on input value', () => { - instance.parentInstance.placeholder = 'This is a test'; + instance.placeholderValue = 'This is a test'; instance.element.value = 'This is a test'; expect(instance.element.style.width).to.not.equal(inputWidth); instance.setWidth(); @@ -297,7 +292,7 @@ describe('components/input', () => { describe('when width is enforced', () => { it('sets the width of the element based on input value', () => { - instance.parentInstance.placeholder = 'This is a test'; + instance.placeholderValue = 'This is a test'; instance.element.value = ''; expect(instance.element.style.width).to.not.equal(inputWidth); instance.setWidth(true); @@ -308,7 +303,7 @@ describe('components/input', () => { describe('when value length is less than 75% of the placeholder length', () => { it('does not set the width of the element', () => { - instance.parentInstance.placeholder = 'This is a test'; + instance.placeholderValue = 'This is a test'; instance.element.value = 'Test'; instance.setWidth(); expect(calcWidthStub.callCount).to.equal(0); diff --git a/src/scripts/src/components/list.js b/src/scripts/src/components/list.js index 27d0f06..eecb53c 100644 --- a/src/scripts/src/components/list.js +++ b/src/scripts/src/components/list.js @@ -1,8 +1,7 @@ export default class List { - constructor(instance, element, classNames) { - this.parentInstance = instance; - this.element = element; - this.classNames = classNames; + constructor({ element }) { + Object.assign(this, { element }); + this.scrollPos = this.element.scrollTop; this.height = this.element.offsetHeight; this.hasChildren = !!this.element.children; diff --git a/src/scripts/src/components/list.test.js b/src/scripts/src/components/list.test.js index b9a349a..574fd47 100644 --- a/src/scripts/src/components/list.test.js +++ b/src/scripts/src/components/list.test.js @@ -1,20 +1,15 @@ import { expect } from 'chai'; import List from './list'; -import { DEFAULT_CLASSNAMES, DEFAULT_CONFIG } from '../constants'; describe('components/list', () => { let instance; - let choicesInstance; let choicesElement; beforeEach(() => { - choicesInstance = { - config: { - ...DEFAULT_CONFIG, - }, - }; choicesElement = document.createElement('div'); - instance = new List(choicesInstance, choicesElement, DEFAULT_CLASSNAMES); + instance = new List({ + element: choicesElement, + }); }); afterEach(() => { @@ -23,17 +18,9 @@ describe('components/list', () => { }); describe('constructor', () => { - it('assigns choices instance to class', () => { - expect(instance.parentInstance).to.eql(choicesInstance); - }); - it('assigns choices element to class', () => { expect(instance.element).to.eql(choicesElement); }); - - it('assigns classnames to class', () => { - expect(instance.classNames).to.eql(DEFAULT_CLASSNAMES); - }); }); describe('clear', () => { diff --git a/src/scripts/src/components/wrapped-element.js b/src/scripts/src/components/wrapped-element.js index e560edf..8e7ad2c 100644 --- a/src/scripts/src/components/wrapped-element.js +++ b/src/scripts/src/components/wrapped-element.js @@ -1,10 +1,9 @@ import { dispatchEvent } from '../lib/utils'; export default class WrappedElement { - constructor(instance, element, classNames) { - this.parentInstance = instance; - this.element = element; - this.classNames = classNames; + constructor({ element, classNames }) { + Object.assign(this, { element, classNames }); + this.isDisabled = false; } diff --git a/src/scripts/src/components/wrapped-element.test.js b/src/scripts/src/components/wrapped-element.test.js index 0475271..a4102fa 100644 --- a/src/scripts/src/components/wrapped-element.test.js +++ b/src/scripts/src/components/wrapped-element.test.js @@ -1,21 +1,17 @@ import { expect } from 'chai'; import WrappedElement from './wrapped-element'; -import { DEFAULT_CLASSNAMES, DEFAULT_CONFIG } from '../constants'; +import { DEFAULT_CLASSNAMES } from '../constants'; describe('components/wrappedElement', () => { let instance; - let choicesInstance; let element; beforeEach(() => { - choicesInstance = { - config: { - ...DEFAULT_CONFIG, - }, - }; - element = document.createElement('select'); - instance = new WrappedElement(choicesInstance, element, DEFAULT_CLASSNAMES); + instance = new WrappedElement({ + element, + classNames: DEFAULT_CLASSNAMES, + }); }); afterEach(() => { @@ -24,10 +20,6 @@ describe('components/wrappedElement', () => { }); describe('constructor', () => { - it('assigns choices instance to class', () => { - expect(instance.parentInstance).to.eql(choicesInstance); - }); - it('assigns choices element to class', () => { expect(instance.element).to.eql(element); }); diff --git a/src/scripts/src/components/wrapped-input.js b/src/scripts/src/components/wrapped-input.js index c8a0a3c..283eb54 100644 --- a/src/scripts/src/components/wrapped-input.js +++ b/src/scripts/src/components/wrapped-input.js @@ -2,16 +2,14 @@ import WrappedElement from './wrapped-element'; import { reduceToValues } from './../lib/utils'; export default class WrappedInput extends WrappedElement { - constructor(instance, element, classNames) { - super(instance, element, classNames); - this.parentInstance = instance; - this.element = element; - this.classNames = classNames; + constructor({ element, classNames, delimiter }) { + super({ element, classNames }); + this.delimiter = delimiter; } set value(items) { const itemsFiltered = reduceToValues(items); - const itemsFilteredString = itemsFiltered.join(this.parentInstance.config.delimiter); + const itemsFilteredString = itemsFiltered.join(this.delimiter); this.element.setAttribute('value', itemsFilteredString); this.element.value = itemsFilteredString; diff --git a/src/scripts/src/components/wrapped-input.test.js b/src/scripts/src/components/wrapped-input.test.js index 8e5476a..d012927 100644 --- a/src/scripts/src/components/wrapped-input.test.js +++ b/src/scripts/src/components/wrapped-input.test.js @@ -2,21 +2,20 @@ import { expect } from 'chai'; import { stub } from 'sinon'; import WrappedElement from './wrapped-element'; import WrappedInput from './wrapped-input'; -import { DEFAULT_CLASSNAMES, DEFAULT_CONFIG } from '../constants'; +import { DEFAULT_CLASSNAMES } from '../constants'; describe('components/wrappedInput', () => { let instance; - let choicesInstance; let element; + const delimiter = '-'; beforeEach(() => { - choicesInstance = { - config: { - ...DEFAULT_CONFIG, - }, - }; element = document.createElement('input'); - instance = new WrappedInput(choicesInstance, element, DEFAULT_CLASSNAMES); + instance = new WrappedInput({ + element, + classNames: DEFAULT_CLASSNAMES, + delimiter, + }); }); afterEach(() => { @@ -25,10 +24,6 @@ describe('components/wrappedInput', () => { }); describe('constructor', () => { - it('assigns choices instance to class', () => { - expect(instance.parentInstance).to.eql(choicesInstance); - }); - it('assigns choices element to class', () => { expect(instance.element).to.eql(element); }); @@ -77,7 +72,7 @@ describe('components/wrappedInput', () => { it('sets delimited value of element based on passed data', () => { expect(instance.element.value).to.equal(''); instance.value = data; - expect(instance.value).to.equal('Value 1,Value 2,Value 3'); + expect(instance.value).to.equal(`Value 1${delimiter}Value 2${delimiter}Value 3`); }); }); }); diff --git a/src/scripts/src/components/wrapped-select.js b/src/scripts/src/components/wrapped-select.js index 526bf44..8c3bf69 100644 --- a/src/scripts/src/components/wrapped-select.js +++ b/src/scripts/src/components/wrapped-select.js @@ -2,11 +2,8 @@ import WrappedElement from './wrapped-element'; import templates from './../templates'; export default class WrappedSelect extends WrappedElement { - constructor(instance, element, classNames) { - super(instance, element, classNames); - this.parentInstance = instance; - this.element = element; - this.classNames = classNames; + constructor({ element, classNames }) { + super({ element, classNames }); } get placeholderOption() { diff --git a/src/scripts/src/components/wrapped-select.test.js b/src/scripts/src/components/wrapped-select.test.js index ec5326c..b85c64f 100644 --- a/src/scripts/src/components/wrapped-select.test.js +++ b/src/scripts/src/components/wrapped-select.test.js @@ -2,20 +2,13 @@ import { expect } from 'chai'; import { stub } from 'sinon'; import WrappedElement from './wrapped-element'; import WrappedSelect from './wrapped-select'; -import { DEFAULT_CLASSNAMES, DEFAULT_CONFIG } from '../constants'; +import { DEFAULT_CLASSNAMES } from '../constants'; describe('components/wrappedSelect', () => { let instance; - let choicesInstance; let element; beforeEach(() => { - choicesInstance = { - config: { - ...DEFAULT_CONFIG, - }, - }; - element = document.createElement('select'); element.id = 'target'; for (let i = 1; i <= 4; i++) { @@ -32,7 +25,10 @@ describe('components/wrappedSelect', () => { } document.body.appendChild(element); - instance = new WrappedSelect(choicesInstance, document.getElementById('target'), DEFAULT_CLASSNAMES); + instance = new WrappedSelect({ + element: document.getElementById('target'), + classNames: DEFAULT_CLASSNAMES, + }); }); afterEach(() => { @@ -41,10 +37,6 @@ describe('components/wrappedSelect', () => { }); describe('constructor', () => { - it('assigns choices instance to class', () => { - expect(instance.parentInstance).to.eql(choicesInstance); - }); - it('assigns choices element to class', () => { expect(instance.element).to.eql(element); });