Skip to content
This repository has been archived by the owner on Apr 15, 2019. It is now read-only.

Commit

Permalink
Merge pull request #625 from LiskHQ/587-add-autocomplete-to-confirm-v…
Browse files Browse the repository at this point in the history
…otes

Add autocomplete module to 'confirm votes modal' - Closes #587
  • Loading branch information
yasharAyari authored Aug 23, 2017
2 parents 917d0b1 + e36c1ad commit 192db4e
Show file tree
Hide file tree
Showing 10 changed files with 481 additions and 22 deletions.
9 changes: 5 additions & 4 deletions features/step_definitions/voting.step.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ defineSupportCode(({ When, Then }) => {
});

When('Search twice for "{searchTerm}" in vote dialog', (searchTerm, callback) => {
element.all(by.css('md-autocomplete-wrap input')).get(0).sendKeys(searchTerm);
waitForElemAndClickIt('ul.md-autocomplete-suggestions li:nth-child(1) md-autocomplete-parent-scope');
element.all(by.css('md-autocomplete-wrap input')).get(0).sendKeys(searchTerm);
waitForElemAndClickIt('ul.md-autocomplete-suggestions li:nth-child(1) md-autocomplete-parent-scope', callback);
element.all(by.css('.votedListSearch input')).get(0).sendKeys(searchTerm);
waitForElemAndClickIt('#votedResult ul li:nth-child(1)');
element.all(by.css('.votedListSearch input')).get(0).sendKeys(searchTerm);
browser.sleep(500);
waitForElemAndClickIt('#votedResult ul li:nth-child(1)', callback);
});

Then('I should see delegates list with {count} lines', (count, callback) => {
Expand Down
1 change: 0 additions & 1 deletion features/voting.feature
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ Feature: Voting tab
And I click "submit button"
Then I should see alert dialog with title "Success" and text "Your votes were successfully submitted. It can take several seconds before they are processed."

@ignore
Scenario: should allow to select delegates in the "Vote" dialog and vote for them
Given I'm logged in as "delegate candidate"
When I click tab number 2
Expand Down
16 changes: 5 additions & 11 deletions src/components/voting/confirmVotes.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { votePlaced } from '../../actions/voting';
import InfoParagraph from '../infoParagraph';
import ActionBar from '../actionBar';
import Fees from '../../constants/fees';
import Autocomplete from './voteAutocomplete';

export class ConfirmVotes extends React.Component {
constructor() {
Expand Down Expand Up @@ -39,20 +40,13 @@ export class ConfirmVotes extends React.Component {

return (
<article>
<h3>Add vote to</h3>
<ul className='voted-list'>
{this.props.votedList.map(item => <li key={item.username}>{item.username}</li>)}
</ul>
<h3>Remove vote from</h3>
<ul className='unvoted-list'>
{this.props.unvotedList.map(item => <li key={item.username}>{item.username}</li>)}
</ul>

<Autocomplete voted={this.props.voted} />
{secondPassphrase}

<InfoParagraph>
You can select up to 33 delegates in one voting turn.
<br />
<div >
You can select up to 33 delegates in one voting turn.
</div>
You can vote for up to 101 delegates in total.
</InfoParagraph>

Expand Down
5 changes: 2 additions & 3 deletions src/components/voting/confirmVotes.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,8 @@ describe('ConfirmVotes', () => {
expect(wrapper.find('InfoParagraph')).to.have.lengthOf(1);
});

it('should render two unordered list of voted and unvoted delegates', () => {
expect(wrapper.find('ul.voted-list li')).to.have.lengthOf(props.votedList.length);
expect(wrapper.find('ul.unvoted-list li')).to.have.lengthOf(props.unvotedList.length);
it('should render Autocomplete', () => {
expect(wrapper.find('VoteAutocomplete')).to.have.lengthOf(1);
});

it('should render an ActionBar', () => {
Expand Down
234 changes: 234 additions & 0 deletions src/components/voting/voteAutocomplete.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
import React from 'react';
import { connect } from 'react-redux';
import Input from 'react-toolbox/lib/input';
import Chip from 'react-toolbox/lib/chip';
import { Card } from 'react-toolbox/lib/card';
import { List, ListItem } from 'react-toolbox/lib/list';
import { voteAutocomplete, unvoteAutocomplete } from '../../utils/api/delegate';
import { addedToVoteList, removedFromVoteList } from '../../actions/voting';
import styles from './voting.css';

export class VoteAutocomplete extends React.Component {
constructor() {
super();
this.state = {
votedSuggestionClass: styles.hidden,
unvotedSuggestionClass: styles.hidden,
votedResult: [],
unvotedResult: [],
votedListSearch: '',
unvotedListSearch: '',
};
}

suggestionStatus(value, name) {
const className = value ? '' : styles.hidden;
setTimeout(() => {
this.setState({ [name]: className });
}, 200);
}
/**
* Update state and call a suitable api to create a list of suggestion
*
* @param {*} name - name of input in react state
* @param {*} value - value that we want save it in react state
*/
search(name, value) {
this.setState({ ...this.state, [name]: value });
clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
if (value.length > 0) {
if (name === 'votedListSearch') {
voteAutocomplete(this.props.activePeer, value, this.props.voted)
.then((res) => {
this.setState({
votedResult: res,
votedSuggestionClass: '',
});
});
} else {
unvoteAutocomplete(value, this.props.voted)
.then((res) => {
this.setState({
unvotedResult: res,
unvotedSuggestionClass: '',
});
});
}
} else {
this.setState({
votedResult: [],
unvotedResult: [],
votedSuggestionClass: styles.hidden,
unvotedSuggestionClass: styles.hidden,
});
}
}, 250);
}
votedSearchKeyDown(event) {
this.keyPress(event, 'votedSuggestionClass', 'votedResult');
}
unvotedSearchKeyDown(event) {
this.keyPress(event, 'unvotedSuggestionClass', 'unvotedResult');
}
/**
* handle key down event on autocomplete inputs
*
* @param {object} event - event of javascript
* @param {*} className - class name of suggestion box
* @param {*} listName - name of the list that we want to use as a suggestion list
*/
keyPress(event, className, listName) {
const selectFunc = listName === 'votedResult' ? 'addToVoted' : 'removeFromVoted';
const selected = this.state[listName].filter(d => d.hovered);
switch (event.keyCode) {
case 40: // 40 is keyCode of arrow down key in keyboard
this.handleArrowDown(this.state[listName], listName);
return false;
case 38: // 38 is keyCode of arrow up key in keyboard
this.handleArrowUp(this.state[listName], listName);
return false;
case 27 : // 27 is keyCode of enter key in keyboard
this.setState({
[className]: styles.hidden,
});
return false;
case 13 : // 27 is keyCode of escape key in keyboard
if (selected.length > 0) {
this[selectFunc](selected[0]);
}
return false;
default:
break;
}
return true;
}
/**
* move to the next item when arrow down is pressed
*
* @param {*} list - suggested list
* @param {*} className - name of the list that we want to use as a suggestion list in react state
*/
handleArrowDown(list, name) {
const selected = list.filter(d => d.hovered);
const index = list.indexOf(selected[0]);
if (selected.length > 0 && list[index + 1]) {
list[index].hovered = false;
list[index + 1].hovered = true;
// document.getElementById('votedResult').scrollTop = 56
} else if (list[index + 1]) {
list[0].hovered = true;
}
this.setState({ [name]: list });
}

/**
* move to the next item when up down is pressed
*
* @param {*} list - suggested list
* @param {*} className - name of the list that we want to use as a suggestion list in react state
*/
handleArrowUp(list, name) {
const selected = list.filter(d => d.hovered);
const index = list.indexOf(selected[0]);
if (index - 1 > -1) {
list[index].hovered = false;
list[index - 1].hovered = true;
}
this.setState({ [name]: list });
}
addToVoted(item) {
this.props.addedToVoteList(item);
this.setState({
votedListSearch: '',
votedSuggestionClass: styles.hidden,
});
}
removeFromVoted(item) {
this.props.removedFromVoteList(item);
this.setState({
unvotedListSearch: '',
unvotedSuggestionClass: styles.hidden,
});
}

render() {
return (
<article>
<h3>Add vote to</h3>
<div>
{this.props.votedList.map(
item => <Chip key={item.username}
deletable
onDeleteClick={this.props.removedFromVoteList.bind(this, item)}>
{item.username}
</Chip>,
)}
</div>
<section className={styles.searchContainer}>
<Input type='text' label='Search by username' name='votedListSearch'
className='votedListSearch' value={this.state.votedListSearch}
onBlur={this.suggestionStatus.bind(this, false, 'votedSuggestionClass')}
onKeyDown={this.votedSearchKeyDown.bind(this)}
onChange={this.search.bind(this, 'votedListSearch')}/>
<Card id='votedResult' className={`${styles.searchResult} ${this.state.votedSuggestionClass}`}>
<List>
{this.state.votedResult.map(
item => <ListItem
key={item.username}
caption={item.username}
selectable={true}
selected={true}
className={item.hovered ? styles.selectedRow : ''}
onClick={this.addToVoted.bind(this, item)} />,
)}
</List>
</Card>
</section>
<h3>Remove vote from</h3>
<div>
{this.props.unvotedList.map(
item => <Chip key={item.username}
deletable
onDeleteClick={this.props.addedToVoteList.bind(this, item)}>
{item.username}
</Chip>,
)}
</div>
<section className={styles.searchContainer}>
<Input type='text' label='Search by username' name='unvotedListSearch'
className='unvotedListSearch' value={this.state.unvotedListSearch}
onBlur={this.suggestionStatus.bind(this, false, 'unvotedSuggestionClass')}
onKeyDown={this.unvotedSearchKeyDown.bind(this)}
onChange={this.search.bind(this, 'unvotedListSearch')}/>
<Card id='unvotedResult' className={`${styles.searchResult} ${this.state.unvotedSuggestionClass}`}>
<List>
{this.state.unvotedResult.map(
item => <ListItem
key={item.username}
caption={item.username}
selectable={true}
selected={true}
className={item.hovered ? styles.selectedRow : ''}
onClick={this.removeFromVoted.bind(this, item)} />,
)}
</List>
</Card>
</section>
</article>
);
}
}

const mapStateToProps = state => ({
votedList: state.voting.votedList,
unvotedList: state.voting.unvotedList,
activePeer: state.peers.data,
});

const mapDispatchToProps = dispatch => ({
addedToVoteList: data => dispatch(addedToVoteList(data)),
removedFromVoteList: data => dispatch(removedFromVoteList(data)),
});

export default connect(mapStateToProps, mapDispatchToProps)(VoteAutocomplete);
Loading

0 comments on commit 192db4e

Please sign in to comment.