UserGroupUserFormByInput.jsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. import { withTranslation } from 'react-i18next';
  4. import { AsyncTypeahead } from 'react-bootstrap-typeahead';
  5. import { debounce } from 'throttle-debounce';
  6. import { createSubscribedElement } from '../../UnstatedUtils';
  7. import AppContainer from '../../../services/AppContainer';
  8. import UserGroupDetailContainer from '../../../services/UserGroupDetailContainer';
  9. import { toastSuccess, toastError } from '../../../util/apiNotification';
  10. class UserGroupUserFormByInput extends React.Component {
  11. constructor(props) {
  12. super(props);
  13. this.state = {
  14. input: '',
  15. applicableUsers: [],
  16. isLoading: false,
  17. searchError: null,
  18. };
  19. this.xss = window.xss;
  20. this.onInputChange = this.onInputChange.bind(this);
  21. this.addUserBySubmit = this.addUserBySubmit.bind(this);
  22. this.validateForm = this.validateForm.bind(this);
  23. this.handleChange = this.handleChange.bind(this);
  24. this.handleSearch = this.handleSearch.bind(this);
  25. this.onKeyDown = this.onKeyDown.bind(this);
  26. this.renderMenuItemChildren = this.renderMenuItemChildren.bind(this);
  27. this.searhApplicableUsersDebounce = debounce(1000, this.searhApplicableUsers);
  28. }
  29. /**
  30. * input user name to add to the group
  31. * @param {string} input
  32. */
  33. onInputChange(input) {
  34. this.setState({ input });
  35. if (input === '') {
  36. this.setState({ applicableUsers: [] });
  37. }
  38. }
  39. async addUserBySubmit(e) {
  40. e.preventDefault();
  41. const { input } = this.state;
  42. try {
  43. await this.props.userGroupDetailContainer.addUserByUsername(input);
  44. toastSuccess(`Added "${this.xss.process(input)}" to "${this.xss.process(this.props.userGroupDetailContainer.state.userGroup.name)}"`);
  45. this.setState({ input: '' });
  46. }
  47. catch (err) {
  48. toastError(new Error(`Unable to add "${this.xss.process(input)}" to "${this.xss.process(this.props.userGroupDetailContainer.state.userGroup.name)}"`));
  49. }
  50. }
  51. validateForm() {
  52. return this.state.input !== '';
  53. }
  54. searhApplicableUsers() {
  55. this.props.userGroupDetailContainer.searhApplicableUsers();
  56. }
  57. /**
  58. * Reflect when forecast is clicked
  59. * @param {string} input
  60. */
  61. handleChange(input) {
  62. this.setState({ input });
  63. }
  64. handleSearch(keyword) {
  65. if (keyword === '') {
  66. return;
  67. }
  68. this.setState({ isLoading: true });
  69. this.searhApplicableUsersDebounce();
  70. }
  71. onKeyDown(event) {
  72. // 13 is Enter key
  73. if (event.keyCode === 13) {
  74. this.addUserBySubmit();
  75. }
  76. }
  77. getEmptyLabel() {
  78. return (this.state.searchError !== null) && 'Error on searching.';
  79. }
  80. renderMenuItemChildren(option, props, index) {
  81. const user = option;
  82. return (
  83. <span>
  84. {user}
  85. </span>
  86. );
  87. }
  88. render() {
  89. const { t } = this.props;
  90. const inputProps = { autoComplete: 'off' };
  91. return (
  92. <form className="form-inline" onSubmit={this.addUserBySubmit}>
  93. <div className="form-group">
  94. <AsyncTypeahead
  95. {...this.props}
  96. id="name-typeahead-asynctypeahead"
  97. ref={(c) => { this.typeahead = c }}
  98. inputProps={inputProps}
  99. isLoading={this.state.isLoading}
  100. labelKey="name"
  101. minLength={0}
  102. options={this.state.applicableUsers} // Search result
  103. emptyLabel={this.getEmptyLabel()}
  104. searchText={(this.state.isLoading ? 'Searching...' : this.getEmptyLabel())}
  105. align="left"
  106. onChange={this.handleChange}
  107. onSearch={this.handleSearch}
  108. onInputChange={this.onInputChange}
  109. onKeyDown={this.onKeyDown}
  110. renderMenuItemChildren={this.renderMenuItemChildren}
  111. caseSensitive={false}
  112. />
  113. </div>
  114. <button type="submit" className="btn btn-sm btn-success" disabled={!this.validateForm()}>{t('add')}</button>
  115. </form>
  116. );
  117. }
  118. }
  119. UserGroupUserFormByInput.propTypes = {
  120. t: PropTypes.func.isRequired, // i18next
  121. appContainer: PropTypes.instanceOf(AppContainer).isRequired,
  122. userGroupDetailContainer: PropTypes.instanceOf(UserGroupDetailContainer).isRequired,
  123. };
  124. /**
  125. * Wrapper component for using unstated
  126. */
  127. const UserGroupUserFormByInputWrapper = (props) => {
  128. return createSubscribedElement(UserGroupUserFormByInput, props, [AppContainer, UserGroupDetailContainer]);
  129. };
  130. export default withTranslation()(UserGroupUserFormByInputWrapper);