Skip to content

Commit

Permalink
feat(Dropdowns): pass more methods to children
Browse files Browse the repository at this point in the history
  • Loading branch information
eddywashere committed Feb 22, 2016
1 parent 96627ef commit 87596e4
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 44 deletions.
42 changes: 23 additions & 19 deletions lib/Dropdown.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,26 @@ class Dropdown extends React.Component {
open: props.open
};

this.openDropdown = this.openDropdown.bind(this);
this.closeDropdown = this.closeDropdown.bind(this);
this.handleDocumentClick = this.handleDocumentClick.bind(this);
this.handleContainerClick = this.handleContainerClick.bind(this);
this.toggleDropdown = this.toggleDropdown.bind(this);
}

componentWillUnmount() {
this.removeDocumentEventListener();
componentDidMount() {
if (this.state.open) {
this.openDropdown();
}
}

removeDocumentEventListener() {
document.removeEventListener('click', this.handleDocumentClick);
componentWillUnmount() {
this.closeDropdown();
}

handleDocumentClick() {
if (this.state.open) {
this.removeDocumentEventListener();
this.setState({
open: !this.state.open
});
this.closeDropdown();
}
}

Expand All @@ -58,22 +58,24 @@ class Dropdown extends React.Component {
}

if (this.state.open) {
document.removeEventListener('click', this.handleDocumentClick);
this.closeDropdown();
} else {
document.addEventListener('click', this.handleDocumentClick);
this.openDropdown();
}
}

closeDropdown() {
this.setState({
open: !this.state.open
open: false
});
document.removeEventListener('click', this.handleDocumentClick);
}

closeDropdown() {
if (this.state.open) {
this.setState({
open: false
});
}
openDropdown() {
this.setState({
open: true
});
document.addEventListener('click', this.handleDocumentClick);
}

renderChildren() {
Expand All @@ -82,9 +84,11 @@ class Dropdown extends React.Component {
return React.cloneElement(
child,
{
isDropdownOpen: this.state.open,
closeDropdown: this.closeDropdown,
handleContainerClick: this.handleContainerClick,
toggleDropdown: this.toggleDropdown
isDropdownOpen: this.state.open,
openDropdown: this.openDropdown,
toggleDropdown: this.toggleDropdown,
}
);
}
Expand Down
42 changes: 40 additions & 2 deletions lib/DropdownItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,46 @@ import omit from 'lodash.omit';

const propTypes = {
children: PropTypes.node,
closeDropdown: PropTypes.func,
disabled: PropTypes.bool,
divider: PropTypes.bool,
El: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
header: PropTypes.bool
handleContainerClick: PropTypes.func,
header: PropTypes.bool,
isDropdownOpen: PropTypes.bool,
toggleDropdown: PropTypes.func
};

class DropdownItem extends React.Component {
constructor(props) {
super(props);

this.onClick = this.onClick.bind(this);
this.onClose = this.onClose.bind(this);
}

onClick(e) {
if (this.props.disabled || this.props.header || this.props.divider) {
return e.preventDefault();
}

if (this.props.onClick) {
this.props.onClick(e);
}

this.onClose();
}

onClose(e) {
if (this.props.onClose) {
this.props.onClose(e);
}

if(this.props.closeDropdown) {
this.props.closeDropdown();
}
}

render() {
let Tagname = 'button';
const {
Expand All @@ -24,6 +57,7 @@ class DropdownItem extends React.Component {
const classes = classNames(
className,
{
'disabled': props.disabled,
'dropdown-item': !divider && !header,
'dropdown-header': header,
'dropdown-divider': divider
Expand All @@ -34,6 +68,7 @@ class DropdownItem extends React.Component {
return (
<El {...props}
className={classes}
onClose={this.onClose}
onClick={this.onClick}>
{children}
</El>
Expand All @@ -47,7 +82,10 @@ class DropdownItem extends React.Component {
}

return (
<Tagname {...props} className={classes}>
<Tagname {...props}
className={classes}
onClose={this.onClose}
onClick={this.onClick}>
{children}
</Tagname>
);
Expand Down
24 changes: 22 additions & 2 deletions lib/DropdownMenu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import omit from 'lodash.omit';

const propTypes = {
children: PropTypes.node.isRequired,
closeDropdown: PropTypes.func,
handleContainerClick: PropTypes.func,
isDropdownOpen: PropTypes.bool,
onClick: PropTypes.func,
openDropdown: PropTypes.func,
right: PropTypes.bool,
toggleDropdown: PropTypes.func
};
Expand All @@ -28,8 +30,26 @@ class DropdownMenu extends React.Component {
}
}

renderChildren() {
return React.Children.map(React.Children.toArray(this.props.children), (child) => {
if (React.isValidElement(child)) {
return React.cloneElement(
child,
{
closeDropdown: this.props.closeDropdown,
handleContainerClick: this.props.handleContainerClick,
isDropdownOpen: this.props.isDropdownOpen,
openDropdown: this.props.openDropdown,
toggleDropdown: this.props.toggleDropdown
}
);
}
return child;
});
}

render() {
const { className, children, right, ...props } = omit(this.props, 'onClick');
const { className, right, ...props } = omit(this.props, 'onClick', 'children');
const classes = classNames(
className,
'dropdown-menu',
Expand All @@ -38,7 +58,7 @@ class DropdownMenu extends React.Component {

return (
<div {...props} className={classes} onClick={this.onClick}>
{children}
{this.renderChildren()}
</div>
);
}
Expand Down
28 changes: 7 additions & 21 deletions test/Dropdown.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ describe('Dropdown', () => {
});

describe('componentWillUnmount', () => {
it('should removeDocumentEventListener', () => {
it('should call closeDropdown', () => {
const wrapper = mount(
<Dropdown>
<DropdownToggle>Toggle</DropdownToggle>
Expand All @@ -102,44 +102,30 @@ describe('Dropdown', () => {
const instance = wrapper.instance();

spyOn(instance, 'componentWillUnmount').and.callThrough();
spyOn(instance, 'removeDocumentEventListener').and.callThrough();
spyOn(instance, 'closeDropdown').and.callThrough();

expect(instance.componentWillUnmount).not.toHaveBeenCalled();
expect(instance.removeDocumentEventListener).not.toHaveBeenCalled();
expect(instance.closeDropdown).not.toHaveBeenCalled();

wrapper.unmount();

expect(instance.componentWillUnmount).toHaveBeenCalled();
expect(instance.removeDocumentEventListener).toHaveBeenCalled();
expect(instance.closeDropdown).toHaveBeenCalled();
});
});

describe('handleDocumentClick', () => {
it('should call removeEventListener when open', () => {
it('should call closeDropdown', () => {
const wrapper = mount(
<Dropdown open>
<DropdownToggle>Toggle</DropdownToggle>
</Dropdown>
);
const instance = wrapper.instance();
spyOn(instance, 'removeDocumentEventListener').and.callThrough();
spyOn(instance, 'closeDropdown').and.callThrough();
instance.handleDocumentClick();

expect(instance.removeDocumentEventListener).toHaveBeenCalled();
expect(wrapper.state('open')).toBe(false);
});

it('should not call removeEventListener when closed', () => {
const wrapper = mount(
<Dropdown>
<DropdownToggle>Toggle</DropdownToggle>
</Dropdown>
);
const instance = wrapper.instance();
spyOn(instance, 'removeDocumentEventListener');
instance.handleDocumentClick();

expect(instance.removeDocumentEventListener).not.toHaveBeenCalled();
expect(instance.closeDropdown).toHaveBeenCalled();
expect(wrapper.state('open')).toBe(false);
});
});
Expand Down
68 changes: 68 additions & 0 deletions test/DropdownItem.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,72 @@ describe('DropdownItem', () => {
expect(wrapper.find('.dropdown-divider').length).toBe(1);
});
});

describe('onClick', () => {
it('should not be called when disabled', () => {
const e = { preventDefault: jasmine.createSpy('preventDefault') };
const wrapper = mount(<DropdownItem disabled>Item</DropdownItem>);
const instance = wrapper.instance();

instance.onClick(e);
expect(e.preventDefault).toHaveBeenCalled();
});

it('should not be called when divider is set', () => {
const e = { preventDefault: jasmine.createSpy('preventDefault') };
const wrapper = mount(<DropdownItem divider/>);
const instance = wrapper.instance();

instance.onClick(e);
expect(e.preventDefault).toHaveBeenCalled();
});

it('should not be called when header item', () => {
const e = { preventDefault: jasmine.createSpy('preventDefault') };
const wrapper = mount(<DropdownItem header>Header</DropdownItem>);
const instance = wrapper.instance();

instance.onClick(e);
expect(e.preventDefault).toHaveBeenCalled();
});

it('should be called not disabled, heading, or divider', () => {
const e = { preventDefault: jasmine.createSpy('preventDefault') };
const onClick = jasmine.createSpy('onClick');
const wrapper = mount(<DropdownItem onClick={onClick.bind(this)}>Click me</DropdownItem>);
const instance = wrapper.instance();

instance.onClick(e);
expect(onClick).toHaveBeenCalled();
});

it('should call onClose', () => {
const wrapper = mount(<DropdownItem>Click me</DropdownItem>);
const instance = wrapper.instance();
spyOn(instance, 'onClose');

instance.onClick({});
expect(instance.onClose).toHaveBeenCalled();
});
});

describe('onClose', () => {
it('should call props.onClose', () => {
const onClose = jasmine.createSpy('onClose');
const wrapper = mount(<DropdownItem onClose={onClose.bind(this)}>Click me</DropdownItem>);
const instance = wrapper.instance();

instance.onClose({});
expect(onClose).toHaveBeenCalled();
});

it('should call props.closeDropdown', () => {
const closeDropdown = jasmine.createSpy('closeDropdown');
const wrapper = mount(<DropdownItem closeDropdown={closeDropdown.bind(this)}>Click me</DropdownItem>);
const instance = wrapper.instance();

instance.onClose({});
expect(closeDropdown).toHaveBeenCalled();
});
});
});

0 comments on commit 87596e4

Please sign in to comment.