EnrollmentTermInput.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. /*
  2. * Copyright (C) 2016 - present Instructure, Inc.
  3. *
  4. * This file is part of Canvas.
  5. *
  6. * Canvas is free software: you can redistribute it and/or modify it under
  7. * the terms of the GNU Affero General Public License as published by the Free
  8. * Software Foundation, version 3 of the License.
  9. *
  10. * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
  11. * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
  13. * details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License along
  16. * with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. import React from 'react'
  19. import PropTypes from 'prop-types'
  20. import _ from 'underscore'
  21. import axios from 'axios'
  22. import I18n from 'i18n!grading_periods'
  23. import TokenInput, {Option as ComboboxOption} from 'react-tokeninput'
  24. const groupByTagType = function(options) {
  25. const now = new Date;
  26. return _.groupBy(options, (option) => {
  27. const noStartDate = !_.isDate(option.startAt);
  28. const noEndDate = !_.isDate(option.endAt);
  29. const started = option.startAt < now;
  30. const ended = option.endAt < now;
  31. if ((started && !ended) ||
  32. (started && noEndDate) ||
  33. (!ended && noStartDate)) {
  34. return 'active';
  35. } else if (!started) {
  36. return 'future';
  37. } else if (ended) {
  38. return 'past';
  39. }
  40. return 'undated';
  41. });
  42. };
  43. let EnrollmentTermInput = React.createClass({
  44. propTypes: {
  45. enrollmentTerms: PropTypes.array.isRequired,
  46. setSelectedEnrollmentTermIDs: PropTypes.func.isRequired,
  47. selectedIDs: PropTypes.array.isRequired
  48. },
  49. handleChange(termIDs) {
  50. this.props.setSelectedEnrollmentTermIDs(termIDs);
  51. },
  52. handleSelect(value, _combobox) {
  53. const termIDs = _.pluck(this.props.enrollmentTerms, "id");
  54. if(_.contains(termIDs, value)) {
  55. const selectedIDs = _.uniq(this.props.selectedIDs.concat([value]));
  56. this.handleChange(selectedIDs);
  57. }
  58. },
  59. handleRemove(termToRemove) {
  60. const selectedTermIDs = _.reject(this.props.selectedIDs, (termID) => {
  61. return termToRemove.id === termID;
  62. });
  63. this.handleChange(selectedTermIDs);
  64. },
  65. selectableTerms() {
  66. return _.reject(this.props.enrollmentTerms, term => _.contains(this.props.selectedIDs, term.id));
  67. },
  68. filteredTagsForType(type) {
  69. const groupedTags = groupByTagType(this.selectableTerms());
  70. return (groupedTags && groupedTags[type]) || [];
  71. },
  72. selectableOptions(type) {
  73. return _.map(this.filteredTagsForType(type), (term) => {
  74. return this.selectableOption(term);
  75. });
  76. },
  77. selectableOption(term) {
  78. return(
  79. <ComboboxOption key={term.id} value={term.id}>
  80. {term.displayName}
  81. </ComboboxOption>
  82. );
  83. },
  84. optionsForAllTypes() {
  85. if (_.isEmpty(this.selectableTerms())) {
  86. return [this.headerOption('none')];
  87. } else {
  88. return _.union(
  89. this.optionsForType('active'),
  90. this.optionsForType('undated'),
  91. this.optionsForType('future'),
  92. this.optionsForType('past')
  93. );
  94. }
  95. },
  96. optionsForType(optionType) {
  97. const header = this.headerOption(optionType);
  98. const options = this.selectableOptions(optionType);
  99. return _.any(options) ? _.union([header], options) : [];
  100. },
  101. headerOption(heading) {
  102. const headerText = {
  103. 'active': I18n.t('Active'),
  104. 'undated': I18n.t('Undated'),
  105. 'future': I18n.t('Future'),
  106. 'past': I18n.t('Past'),
  107. 'none': I18n.t('No unassigned terms')
  108. }[heading];
  109. return(
  110. <ComboboxOption
  111. className='ic-tokeninput-header'
  112. value={heading}
  113. key={heading}
  114. >
  115. {headerText}
  116. </ComboboxOption>
  117. );
  118. },
  119. suppressKeys(event) {
  120. const code = event.keyCode || event.which;
  121. if (code === 13) {
  122. event.preventDefault();
  123. }
  124. },
  125. selectedEnrollmentTerms() {
  126. return _.map(this.props.selectedIDs, (id) => {
  127. let term = _.findWhere(this.props.enrollmentTerms, { id: id });
  128. let termForDisplay = _.extend({}, term);
  129. termForDisplay.name = term.displayName;
  130. return termForDisplay;
  131. });
  132. },
  133. render() {
  134. return (
  135. <div className = 'ic-Form-control'
  136. onKeyDown = {this.suppressKeys}>
  137. <label className = 'ic-Label'
  138. title = {I18n.t('Attach terms')}
  139. aria-label = {I18n.t('Attach terms')}>
  140. {I18n.t('Attach terms')}
  141. </label>
  142. <div className='ic-Input'>
  143. <TokenInput menuContent = {this.optionsForAllTypes()}
  144. selected = {this.selectedEnrollmentTerms()}
  145. onChange = {this.handleChange}
  146. onSelect = {this.handleSelect}
  147. onRemove = {this.handleRemove}
  148. onInput = {function(){}}
  149. value = {true}
  150. showListOnFocus = {true}
  151. ref = 'input' />
  152. </div>
  153. </div>
  154. );
  155. }
  156. });
  157. export default EnrollmentTermInput