Skip to content

Commit

Permalink
Release Candidate 4.2.0 (#98)
Browse files Browse the repository at this point in the history
* feature/validate-instance (#94)

* validate instance

added validate() public method which higlights the ui and returns a boolean whether the instance was valid

* validate instance

ammend to last commit. somehow line was missing

* validate-instance fix for callback functions

calling instance.validate() now works in callbacks. precised the condition when to remove the validation error. Updated callback demo

* Pull request feedback integrated

* Reverted 15-callbacks demo as the programmatic invalidate feature will get its own demo file.

* add programmatic validation demo

* update demos list

* prevent native ui on iOS if disabled

* onoptionclick callback

* changelog

* generate bundle

Co-authored-by: Alex Milde <[email protected]>
  • Loading branch information
patrickkunka and alexmilde authored Feb 2, 2020
1 parent 525f775 commit 96c3730
Show file tree
Hide file tree
Showing 24 changed files with 289 additions and 49 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
Changelog
=========

## 4.2.0
- Fixes issue where native select UI would be shown on iOS, regardless of whether `behavior.useNativeUiOnMobile` was set to `false`.
- Adds a `.validate()` method to programmatically validate an instance, and a new demo (#16).
- Adds a new callback `onOptionClick`.

## 4.1.1
- Fixes issue introduced in 4.1.0 where UI no longer closed on select by default
- Fixes issue introduced in 4.1.0 where clicking an option while `behavior.openOnFocus` set would close the UI without selecting any option.
Expand Down
38 changes: 37 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Check out the **[demos](https://demos.kunkalabs.com/easydropdown/)** to see what
- [Anatomy of EasyDropDown](#anatomy-of-easydropdown)
- [Configuration Options](#configuration-options)
- [Available Options](#available-options)
- [API Methods](#configuration-options)
- [API Methods](#api-methods)
- [React Example](#react-example)
- [CSS Modules Example](#css-modules-example)
- [TypeScript Support](#typescript-support)
Expand Down Expand Up @@ -427,6 +427,7 @@ const edd = easydropdown.all({
- [onClose](#onclose)
- [onOpen](#onopen)
- [onSelect](#onselect)
- [onOptionClick](#onoptionclick)
#### `classNames`
Expand Down Expand Up @@ -670,6 +671,26 @@ const edd = easydropdown(selectElement, {
});
```
### `onClickOption`
| Type | Default |
|------------|---------|
| `function` | `null` |
An optional callback function to be invoked whenever an option is clicked, regardless of whether that option is already selected, selectable or disabled. The clicked option's value is passed as the first argument to the callback.
See the [Callbacks](https://demos.kunkalabs.com/easydropdown/15-callbacks.html) demo for a working example.
##### Example: Adding an `onClickOption` callback
```js
const edd = easydropdown(selectElement, {
callbacks: {
onClickOption: value => console.log(`clicked option ${value}`)
}
});
```
## API Methods
The `EasydropdownFacade` instance returned from the factory function exposes several API methods for programmatic control of the dropdown, and instance destruction.
Expand Down Expand Up @@ -716,6 +737,21 @@ const edd = easydropdown(selectElement);
edd.refresh();
```
### validate()
`.validate()`
Validates the instance and returns a boolean whether validation succeeded. Helpful if you need to validate without hitting the form submit button.
```js
const edd = easydropdown(selectElement);
const isValid = edd.validate();
console.log(isValid); // false
```
### destroy()
`.destroy()`
Expand Down
2 changes: 1 addition & 1 deletion bundle/easydropdown.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion bundle/easydropdown.js.map

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions demos/15-callbacks.html
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ <h1 class="heading">15. Callbacks</h1>
},
onSelect: function(value) {
console.log('select', value);
},
onOptionClick: function(value) {
console.log('click option', value);
}
}
});
Expand Down
70 changes: 70 additions & 0 deletions demos/16-programmatic-validation.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<!doctype HTML>
<html>
<head>
<title>Programmatic Validation | EasyDropDown Demo</title>

<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">

<link href="./style.css" rel="stylesheet"/>
<link id="theme-sheet" href="./themes/flax.css" rel="stylesheet"/>
<link rel="shortcut icon" type="image/png" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAb1BMVEUAAABCiZtUyORUyORCiZtUyORCiZtUyORCiZtUyORUyORCiZtUyORCiZtUyOREkKNUyORUyORUyORCiZtUyORUyORCiZtUyORCiZtUyORCiZtDjaBFlalHma1KpbtLqcBOtM1QuNJSwNtTxN9UyOQlYZhqAAAAGnRSTlMAEBAgMDBAQFBQYHBwgICPj5+vv7/P39/v793jxf8AAACqSURBVHja1ZJdF4IgDIYnZaaVpWbKrCzl///GIokYH3btc/fuPBwGGywCQeH5HyEVyaxwAsHZjFDLfA4LfPXJeVBIVU4CQvHNDfMKNdO59Ak8NvLeI2Qkx45Q0NwwSxiO1oHSEq64ta8kwh2xjeymDeGBbyrn2bowdCg5+D4OpgYmNlQQmRJ6VLSRMzxZeKKmcscPYuzwx85ZIBA3NFnbKwg9Ei4jFTgsgRepVztePP0rPgAAAABJRU5ErkJggg==">

<script src="./scripts/themeSwitcher.js"></script>
<script src="./scripts/submitHandler.js"></script>
</head>
<body>
<main class="main">
<h2 class="sub-heading">EasyDropDown Demo</h2>

<h1 class="heading">16. Programmatic Validation</h1>

<form class="demo-container" method="post">
<select required id="test">
<option value="">Expiry Month</option>
<option>January</option>
<option>February</option>
<option>March</option>
<option>April</option>
<option>May</option>
<option>June</option>
<option>July</option>
<option>August</option>
<option>September</option>
<option>October</option>
<option>November</option>
<option>December</option>
</select>

<button type="button" class="button" id="validate">Validate</button>
</form>

<div class="theme-switcher">
<div class="theme-switcher-label">Theme:</div>

<div class="theme-switcher-options">
<button type="button" data-theme="./themes/flax.css" class="active">Flax</button> |
<button type="button" data-theme="./themes/beanstalk.css">Beanstalk</button> |
<button type="button" data-theme="./themes/ivy.css">Ivy</button>
</div>
</div>
</main>

<footer class="footer">
<a target="_blank" href="https://github.com/patrickkunka/easydropdown">EasyDropDown v4</a>
<a target="_blank" href="https://www.kunkalabs.com">&copy; KunkaLabs Ltd</a>
</footer>

<script src="./easydropdown.js"></script>
<script>
var edd = easydropdown('#test');
var validateButton = document.querySelector('#validate');

validateButton.addEventListener('click', function() {
var isValid = edd.validate();

if (isValid) alert('Valid!');
});
</script>
</body>
</html>
2 changes: 1 addition & 1 deletion demos/easydropdown.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion demos/easydropdown.js.map

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions demos/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ <h1>EasyDropDown Demos</h1>
<li><a href="./13-live-updates.html">Live Updates</a></li>
<li><a href="./14-loop.html">Loop</a></li>
<li><a href="./15-callbacks.html">Callbacks</a></li>
<li><a href="./16-programmatic-validation.html">Programmatic Validation</a></li>
</ol>
</body>
</html>
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "easydropdown",
"version": "4.1.1",
"version": "4.2.0",
"description": "A lightweight library for building beautiful styleable select elements",
"author": "KunkaLabs Limited",
"private": false,
Expand Down
8 changes: 5 additions & 3 deletions src/Config/Callbacks.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import ICallback from './Interfaces/ICallback';
import ISelectCallback from './Interfaces/ISelectCallback';

class Callbacks {
public onOpen: ICallback = null;
public onClose: ICallback = null;
public onSelect: ICallback = null;
public onOpen: ICallback = null;
public onClose: ICallback = null;
public onSelect: ISelectCallback = null;
public onOptionClick: ISelectCallback = null;

constructor() {
Object.seal(this);
Expand Down
10 changes: 10 additions & 0 deletions src/Config/Interfaces/ICallbacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ interface ICallbacks {
*/

onSelect?: ISelectCallback;

/**
* An optional callback function to be invoked whenever
* an option is clicked, regardless of whether that option
* is already selected, selectable or disabled. The clicked
* option's value is passed as the first argument to the
* callback.
*/

onOptionClick?: ISelectCallback;
}

export default ICallbacks;
60 changes: 55 additions & 5 deletions src/Easydropdown/Easydropdown.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import {spy} from 'sinon';

import Renderer from '../Renderer/Renderer';

import Easydropdown from './Easydropdown';
import Easydropdown from './Easydropdown';

const createSelect = () => {
const createSelect = (selectAttributes: string[] = [], firstOptionAttributes: string[] = []) => {
const parent = document.createElement('div');

parent.innerHTML = (`
<select>
<option>A</option>
<select ${selectAttributes.join(' ')}>
<option ${firstOptionAttributes.join(' ')}>A</option>
<option>B</option>
<option>C</option>
</select>
Expand Down Expand Up @@ -53,12 +53,14 @@ describe('Easydropdown', () => {
const onOpenSpy = spy();
const onCloseSpy = spy();
const onSelectSpy = spy();
const onOptionClickSpy = spy();

const edd = new Easydropdown(select, {
callbacks: {
onOpen: onOpenSpy,
onClose: onCloseSpy,
onSelect: onSelectSpy
onSelect: onSelectSpy,
onOptionClick: onOptionClickSpy
}
});

Expand All @@ -73,6 +75,12 @@ describe('Easydropdown', () => {
edd.actions.close();

assert.isTrue(onCloseSpy.called);

edd.actions.focusOption(1);
edd.actions.startClickSelecting();
edd.actions.selectOption(1);

assert.isTrue(onOptionClickSpy.calledWith('B'));
});

it('does not invoke consumer callbacks if not provided', () => {
Expand All @@ -86,6 +94,10 @@ describe('Easydropdown', () => {

edd.actions.close();

edd.actions.focusOption(1);
edd.actions.startClickSelecting();
edd.actions.selectOption(1);

assert.isTrue(true);
});

Expand Down Expand Up @@ -152,4 +164,42 @@ describe('Easydropdown', () => {
assert.equal(edd.dom.item.length, 7);
});
});

describe('.validate()', () => {

it('validates a required select without placeholder', () => {
const select = createSelect(['required']);
const edd = new Easydropdown(select, {});

assert.equal(edd.validate(), true);
});

it('validates a required select with placeholder', () => {
const select = createSelect(['required'], ['data-placeholder']);
const edd = new Easydropdown(select, {});

assert.equal(edd.validate(), false);
edd.actions.selectOption(1);
assert.equal(edd.validate(), true);
});

it('validates a non required select with placeholder', () => {
const select = createSelect([], ['data-placeholder']);
const edd = new Easydropdown(select, {});

assert.equal(edd.validate(), true);
edd.actions.selectOption(1);
assert.equal(edd.validate(), true);
});

it('validates a non required select', () => {
const select = createSelect();
const edd = new Easydropdown(select, {});

assert.equal(edd.validate(), true);
edd.actions.selectOption(1);
assert.equal(edd.validate(), true);
});

});
});
36 changes: 29 additions & 7 deletions src/Easydropdown/Easydropdown.ts
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import merge from 'helpful-merge';
import Config from '../Config/Config';
import ICallback from '../Config/Interfaces/ICallback';
import IConfig from '../Config/Interfaces/IConfig';
import ISelectCallback from '../Config/Interfaces/ISelectCallback';
import bindEvents from '../Events/bindEvents';
import EventBinding from '../Events/EventBinding';
import Dom from '../Renderer/Dom';
Expand Down Expand Up @@ -99,6 +98,16 @@ class Easydropdown {
Renderer.queryDomRefs(this.dom, ['group', 'option', 'item']);
}

public validate(): boolean {
if (!this.state.isRequired || this.state.hasValue) {
return true;
}

this.actions.invalidate();

return false;
}

public destroy(): void {
this.timers.clear();
this.eventBindings.forEach(binding => binding.unbind());
Expand All @@ -112,12 +121,12 @@ class Easydropdown {
private handleStateUpdate(state: State, key: keyof State): void {
const {callbacks} = this.config;

let cb: ICallback;

this.renderer.update(state, key);

switch (key) {
case 'bodyStatus':
case 'bodyStatus': {
let cb: ICallback;

if (state.isOpen) {
cb = callbacks.onOpen;
} else {
Expand All @@ -127,10 +136,23 @@ class Easydropdown {
if (typeof cb === 'function') cb();

break;
case 'selectedIndex':
cb = callbacks.onSelect;
}
case 'selectedIndex': {
const cb = callbacks.onSelect;

if (typeof cb === 'function') cb(state.value);

break;
}
case 'isClickSelecting': {
const cb = callbacks.onOptionClick;

if (state[key] === false) {
const nextValue = state.getOptionFromIndex(state.focusedIndex).value;

if (typeof cb === 'function') (cb as ISelectCallback)(state.value);
if (typeof cb === 'function') cb(nextValue);
}
}
}
}
}
Expand Down
Loading

0 comments on commit 96c3730

Please sign in to comment.