@instructure/quiz-interactions
Version:
A React UI component Library for quiz interaction types.
200 lines (181 loc) • 6.32 kB
JavaScript
/** @jsx jsx */
import {Component} from 'react'
import PropTypes from 'prop-types'
import map from 'lodash/map'
import find from 'lodash/find'
import indexOf from 'lodash/indexOf'
import update from 'immutability-helper'
import {jsx} from '@instructure/emotion'
import Wordbank from '../../fill_blank/drag_components/Wordbank'
import TakeStem from '../../fill_blank/Take/take_components/TakeStem'
import generateStyle from './styles'
import generateComponentTheme from './theme'
import {withStyleOverrides} from '@instructure/quiz-common/util/withStyleOverrides'
/**
---
category: RichFillInTheBlank
---
Rich Fill in the Blank Take component
```jsx_example
function Example (props) {
const exampleProps = {
itemBody: `<p><span id="blank_fitb_uuid1"></span> Columbus sailed in
<span id="blank_fitb_uuid2"></span> from the country of
<span id="blank_fitb_uuid3"></span> on the continent of
<span id="blank_fitb_uuid4"></span> across the
<span id="blank_fitb_uuid5"></span> and found...
<span id="blank_fitb_uuid6"></span>!!! Where the people were "
<span id="blank_fitb_uuid7"></span>"</p>`,
interactionData: {
blanks: [{
id: 'fitb_uuid1',
answerType: 'openEntry'
}, {
id: 'fitb_uuid2',
answerType: 'openEntry'
}, {
id: 'fitb_uuid3',
answerType: 'openEntry'
}, {
id: 'fitb_uuid4',
answerType: 'openEntry'
}, {
id: 'fitb_uuid5',
answerType: 'openEntry'
}, {
id: 'fitb_uuid6',
choices: [
{ id: 'choice_uuid11_brazil', position: 1, itemBody: 'Brazil' },
{ id: 'choice_uuid12_austria', position: 2, itemBody: 'Austria' },
{ id: 'choice_uuid13_america', position: 3, itemBody: 'America' }
],
answerType: 'wordbank'
}, {
id: 'fitb_uuid7',
choices: [
{ id: 'choice_uuid11_peaceful', position: 1, itemBody: 'peaceful' },
{ id: 'choice_uuid12_war-torn', position: 2, itemBody: 'war-torn' },
{ id: 'choice_uuid13_confused', position: 3, itemBody: 'confused' }
],
answerType: 'dropdown'
}]
},
userResponse: {
value: [
{ id: 'fitb_uuid1', value: 'Christopher', type: 'Text'},
{ id: 'fitb_uuid2', value: '1493', type: 'Text'},
{ id: 'fitb_uuid3', value: 'Span', type: 'Text'},
{ id: 'fitb_uuid4', value: 'Europe', type: 'Text'},
{ id: 'fitb_uuid5', value: 'Atlantic', type: 'Text'},
{ id: 'fitb_uuid6', value: 'choice_uuid12_austria', type: 'Text'},
{ id: 'fitb_uuid7', value: 'choice_uuid13_confused', type: 'Text'}
]
}
}
return (
<DndProvider backend={HTML5Backend}>
<FillBlankTake {...exampleProps} {...props} />
</DndProvider>
)
}
<SettingsSwitcher locales={LOCALES}>
<TakeStateProvider>
<Example />
</TakeStateProvider>
</SettingsSwitcher>
```
**/
(generateStyle, generateComponentTheme)
export default class RichFillBlankTake extends Component {
static displayName = 'RichFillBlankTake'
static componentId = `Quizzes${this.displayName}`
static propTypes = {
handleResponseUpdate: PropTypes.func,
interactionData: PropTypes.object.isRequired,
itemBody: PropTypes.string.isRequired,
userResponse: PropTypes.object.isRequired,
readOnly: PropTypes.bool,
styles: PropTypes.object,
}
static defaultProps = {
handleResponseUpdate: void 0,
readOnly: false,
}
moveChoiceToWordbank = (blankId, choiceId) => {
const responses = this.props.userResponse.value
const blank = find(responses, {id: blankId})
// Removes the answer for blank
if (blank) {
const blankIndex = indexOf(map(this.props.userResponse.value, 'id'), blankId)
const newResponses = update(responses, {
[blankIndex]: {
$merge: {value: null},
},
})
this.props.handleResponseUpdate(newResponses)
}
}
allWordBankChoices = () => {
if (this.props.interactionData.wordBankChoices) {
return this.props.interactionData.wordBankChoices
}
const wordbankBlanks = this.props.interactionData.blanks.filter(
blank => blank.answerType === 'wordbank',
)
return wordbankBlanks.map(blank => blank.choices).flat()
}
remainingWordBankChoices() {
let choices = [...this.allWordBankChoices()]
if (this.props.interactionData.reuseWordBankChoices) {
// only unique choices (by itemBody)
choices = choices.filter(
(value, index, array) =>
array.findIndex(choice => choice.itemBody === value.itemBody) === index,
)
} else {
// filter the choices which are already used
this.props.userResponse.value.forEach(response => {
const choiceIndex = choices.findIndex(choice =>
[choice.id, choice.itemBody].includes(response.value),
)
if (choiceIndex > -1) {
choices.splice(choiceIndex, 1)
}
})
}
return choices
}
renderWordbank() {
const blanks = this.props.interactionData.blanks || []
if (blanks.find(blank => ['wordbank', 'dropdown'].includes(blank.answerType))) {
return (
<Wordbank
blanks={blanks}
response={this.props.userResponse.value}
reuseWordBankChoices={this.props.interactionData.reuseWordBankChoices}
returnChoiceToWordbank={this.moveChoiceToWordbank}
wordBankChoices={this.remainingWordBankChoices()}
/>
)
}
}
render() {
return (
<div css={this.props.styles.question}>
<div css={this.props.styles.headerSection}>
<TakeStem
onUpdate={this.props.handleResponseUpdate}
itemBody={this.props.itemBody}
interactionData={this.props.interactionData}
readOnly={this.props.readOnly}
returnChoiceToWordbank={this.moveChoiceToWordbank}
richFITB
userResponses={this.props.userResponse.value}
wordBankChoices={this.remainingWordBankChoices()}
/>
</div>
{this.renderWordbank()}
</div>
)
}
}