Skip to content

Commit

Permalink
Merge branch 'Mairu-#263-add-property-onBreakLabelClick'
Browse files Browse the repository at this point in the history
  • Loading branch information
MonsieurV committed Dec 3, 2021
2 parents d6caa47 + 0aa8f4b commit c35167b
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 23 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

## >= 8.1.0

- A new prop `onClick` has been added. It is a callback for any click on the component. It exposes information on the part clicked (for eg. `isNext` for when next control is clicked or `isBreak` for a break clicked), the next expected page `nextSelectedPage` & others. Can return `false` to prevent any page change or a number to override the page to jump to. Just return nothing (or `undefined`) to let default behavior take place. (see: https://github.com/AdeleD/react-paginate/issues/263)
- Prevent breaks to be displayed when both `pageRangeDisplayed` and `marginPagesDisplayed` are 0

## >= 8.0.3
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ Finally there is this **[CodePen demo](https://codepen.io/monsieurv/pen/yLoMxYQ)
| `breakLabel` | `Node` | Label for ellipsis. |
| `breakClassName` | `String` | The classname on tag `li` of the ellipsis element. |
| `breakLinkClassName` | `String` | The classname on tag `a` of the ellipsis element. |
| `onPageChange` | `Function` | The method to call when a page is clicked. Exposes the current page object as an argument. |
| `onPageChange` | `Function` | The method to call when a page is changed. Exposes the current page object as an argument. |
| `onClick` | `Function` | A callback for any click on the component. Exposes information on the part clicked (for eg. `isNext` for next control), the next expected page `nextSelectedPage` & others. Can return `false` to prevent any page change or a number to override the page to jump to. |
| `onPageActive` | `Function` | The method to call when an active page is clicked. Exposes the active page object as an argument. |
| `initialPage` | `Number` | The initial page selected, in [uncontrolled mode](https://reactjs.org/docs/uncontrolled-components.html). Do not use with `forcePage` at the same time. |
| `forcePage` | `Number` | To override selected page with parent prop. Use this if you want to [control](https://reactjs.org/docs/forms.html#controlled-components) the page from your app state. |
Expand Down
6 changes: 6 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@ When `forcePage` is provided, the component should be absolutely controlled.
It should not be possible to let user clicks change the page (without the controlled state changing from the listening parent component).

See https://github.com/AdeleD/react-paginate/issues/198#issuecomment-941295225

- Detail several props:
- theme
- callbacks like onClick
- explain controlled `page`
- etc.
57 changes: 57 additions & 0 deletions __tests__/PaginationBoxView-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2224,4 +2224,61 @@ describe('Test custom props', () => {
).toBe('2');
});
});

describe('onClick', () => {
it('should use the onClick prop when defined', () => {
const myOnClick = jest.fn(() => false);
const pagination = ReactTestUtils.renderIntoDocument(
<PaginationBoxView
onClick={myOnClick}
initialPage={10}
pageCount={20}
marginPagesDisplayed={2}
pageRangeDisplayed={5}
/>
);
const breakItem =
ReactDOM.findDOMNode(pagination).querySelector('li.break a');
ReactTestUtils.Simulate.click(breakItem);

expect(myOnClick).toHaveBeenCalledWith(
expect.objectContaining({
index: 2,
selected: 10,
event: expect.objectContaining({ target: expect.any(Element) }),
isPrevious: false,
isNext: false,
isBreak: true,
isActive: false,
})
);

// page should not change because onClick returned false
expect(
ReactDOM.findDOMNode(pagination).querySelector('.selected a')
.textContent
).toBe('11');
});

it('should use the return value from onClick to change page', () => {
const myOnClick = () => 5;
const pagination = ReactTestUtils.renderIntoDocument(
<PaginationBoxView
onClick={myOnClick}
initialPage={10}
pageCount={20}
marginPagesDisplayed={2}
pageRangeDisplayed={5}
/>
);
const breakItem =
ReactDOM.findDOMNode(pagination).querySelector('li.break a');
ReactTestUtils.Simulate.click(breakItem);

expect(
ReactDOM.findDOMNode(pagination).querySelector('.selected a')
.textContent
).toBe('6');
});
});
});
9 changes: 9 additions & 0 deletions demo/js/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ export class App extends Component {
}

handlePageClick = (data) => {
console.log('onPageChange', data);
let selected = data.selected;
let offset = Math.ceil(selected * this.props.perPage);

Expand Down Expand Up @@ -156,6 +157,14 @@ export class App extends Component {
}
hrefAllControls
forcePage={currentPage}
onClick={(clickEvent) => {
console.log('onClick', clickEvent);
// Return false to prevent standard page change,
// return false; // --> Will do nothing.
// return a number to choose the next page,
// return 4; --> Will go to page 5 (index 4)
// return nothing (undefined) to let standard behavior take place.
}}
/>
</nav>
</div>
Expand Down
2 changes: 1 addition & 1 deletion dist/react-paginate.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/react-paginate.js.map

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,20 @@ export interface ReactPaginateProps {
*/
onPageActive?(selectedItem: { selected: number }): void;

/**
* The method to call when an active page is clicked. Exposes the active page object as an argument.
*/
onClick?(clickEvent: {
index: number | null;
selected: number;
nextSelectedPage: number | undefined;
event: object;
isPrevious: boolean;
isNext: boolean;
isBreak: boolean;
isActive: boolean;
}): boolean | number | void;

/**
* The initial page selected.
*/
Expand Down
93 changes: 73 additions & 20 deletions react_components/PaginationBoxView.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export default class PaginationBoxView extends Component {
hrefAllControls: PropTypes.bool,
onPageChange: PropTypes.func,
onPageActive: PropTypes.func,
onClick: PropTypes.func,
initialPage: PropTypes.number,
forcePage: PropTypes.number,
disableInitialCallback: PropTypes.bool,
Expand Down Expand Up @@ -165,33 +166,41 @@ export default class PaginationBoxView extends Component {
}
}

handlePreviousPage = (evt) => {
handlePreviousPage = (event) => {
const { selected } = this.state;
evt.preventDefault ? evt.preventDefault() : (evt.returnValue = false);
if (selected > 0) {
this.handlePageSelected(selected - 1, evt);
}

this.handleClick(event, null, selected > 0 ? selected - 1 : undefined, {
isPrevious: true,
});
};

handleNextPage = (evt) => {
handleNextPage = (event) => {
const { selected } = this.state;
const { pageCount } = this.props;

evt.preventDefault ? evt.preventDefault() : (evt.returnValue = false);
if (selected < pageCount - 1) {
this.handlePageSelected(selected + 1, evt);
}
this.handleClick(
event,
null,
selected < pageCount - 1 ? selected + 1 : undefined,
{ isNext: true }
);
};

handlePageSelected = (selected, evt) => {
evt.preventDefault ? evt.preventDefault() : (evt.returnValue = false);

handlePageSelected = (selected, event) => {
if (this.state.selected === selected) {
this.callActiveCallback(selected);
this.handleClick(event, null, undefined, { isActive: true });
return;
}

this.setState({ selected: selected });
this.handleClick(event, null, selected);
};

handlePageChange = (selected) => {
if (this.state.selected === selected) {
return;
}
this.setState({ selected });

// Call the callback with the new selected item:
this.callCallback(selected);
Expand Down Expand Up @@ -220,14 +229,58 @@ export default class PaginationBoxView extends Component {
return backwardJump < 0 ? 0 : backwardJump;
}

handleBreakClick = (index, evt) => {
evt.preventDefault ? evt.preventDefault() : (evt.returnValue = false);
handleClick = (
event,
index,
nextSelectedPage,
{
isPrevious = false,
isNext = false,
isBreak = false,
isActive = false,
} = {}
) => {
event.preventDefault ? event.preventDefault() : (event.returnValue = false);
const { selected } = this.state;
const { onClick } = this.props;

let newPage = nextSelectedPage;

if (onClick) {
const onClickReturn = onClick({
index,
selected,
nextSelectedPage,
event,
isPrevious,
isNext,
isBreak,
isActive,
});
if (onClickReturn === false) {
// We abord standard behavior and let parent handle
// all behavior.
return;
}
if (Number.isInteger(onClickReturn)) {
// We assume parent want to go to the returned page.
newPage = onClickReturn;
}
}

if (newPage !== undefined) {
this.handlePageChange(newPage);
}
};

handleBreakClick = (index, event) => {
const { selected } = this.state;

this.handlePageSelected(
this.handleClick(
event,
index,
selected < index ? this.getForwardJump() : this.getBackwardJump(),
evt
{ isBreak: true }
);
};

Expand Down Expand Up @@ -258,7 +311,7 @@ export default class PaginationBoxView extends Component {

callCallback = (selectedItem) => {
if (
typeof this.props.onPageChange !== 'undefined' &&
this.props.onPageChange !== undefined &&
typeof this.props.onPageChange === 'function'
) {
this.props.onPageChange({ selected: selectedItem });
Expand All @@ -267,7 +320,7 @@ export default class PaginationBoxView extends Component {

callActiveCallback = (selectedItem) => {
if (
typeof this.props.onPageActive !== 'undefined' &&
this.props.onPageActive !== undefined &&
typeof this.props.onPageActive === 'function'
) {
this.props.onPageActive({ selected: selectedItem });
Expand Down

0 comments on commit c35167b

Please sign in to comment.