@instructure/quiz-taking
Version:
254 lines (220 loc) • 7.41 kB
JavaScript
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {Button} from '@instructure/ui-buttons'
import {Checkbox} from '@instructure/ui-checkbox'
import {View} from '@instructure/ui-view'
import {sessionStore} from '@instructure/quiz-core/common/util/sessionStore'
import {SecondaryNavBarButton} from '@instructure/quiz-core/common/components/layout/navbar/SecondaryNavBarButton/index'
import t from '@instructure/quiz-i18n/format-message'
import {PresentationContent, ScreenReaderContent} from '@instructure/ui-a11y-content'
import {TopNavBar} from '@instructure/ui-top-nav-bar'
import {withStyleOverrides} from '@instructure/quiz-common/util/withStyleOverrides'
import generateStyle from './style'
class BaseTakeButton extends Component {
static displayName = 'TakeButton'
static componentId = `Quizzes${this.displayName}`
static propTypes = {
allowBacktracking: PropTypes.bool.isRequired,
currentQuestionIsLast: PropTypes.bool.isRequired,
currentSessionItemPosition: PropTypes.number.isRequired,
isOneQuestionAtATime: PropTypes.bool.isRequired,
itemId: PropTypes.string,
largeFormatResponse: PropTypes.shape({
value: PropTypes.string,
}),
onSubmitQuizSession: PropTypes.func, // consumer provided
nextQuestion: PropTypes.func.isRequired,
quizSessionId: PropTypes.string,
screenreaderNotification: PropTypes.func.isRequired,
submitQuiz: PropTypes.func.isRequired,
disabled: PropTypes.bool,
// This object is passed through; TakeButton does not care about shape
userResponse: PropTypes.object, // eslint-disable-line react/forbid-prop-types
withConfirm: PropTypes.func.isRequired,
isFooter: PropTypes.bool,
iceTopNavBarEnabled: PropTypes.bool,
// eslint-disable-next-line react/forbid-prop-types,react/require-default-props
styles: PropTypes.object,
isPassage: PropTypes.bool.isRequired,
isRequiredItem: PropTypes.bool,
isSurvey: PropTypes.bool,
}
static defaultProps = {
disabled: false,
itemId: null,
largeFormatResponse: null,
/* v8 ignore start */
onSubmitQuizSession: () => {},
/* v8 ignore end */
quizSessionId: null,
userResponse: {},
isFooter: false,
iceTopNavBarEnabled: false,
isRequiredItem: false,
isSurvey: false,
}
submitQuiz = () => {
this.props.submitQuiz(this.props.quizSessionId, this.props.onSubmitQuizSession)
}
confirmNextKey = () => {
return `quiz_session_${this.props.quizSessionId}_confirm_next`
}
confirmNext = () => {
return sessionStore.get(this.confirmNextKey()) === 'false' ? false : true
}
toggleConfirmNext = () => {
sessionStore.set(this.confirmNextKey(), String(!this.confirmNext()))
}
unsetConfirmNext = () => {
sessionStore.set(this.confirmNextKey())
}
withConfirmOptions = () => {
const confirmBody = (
<View as="div">
{t(
'You have not answered this question and this quiz does not allow backtracking. Proceed anyway?',
)}
<View as="div" margin="medium 0 0 0">
<Checkbox
label={t('Do not ask me again for this attempt')}
size="small"
onChange={this.toggleConfirmNext}
/>
</View>
</View>
)
return {
title: t('Are you sure?'),
text: confirmBody,
continueText: t('Confirm'),
cancelText: t('Cancel'),
onCancel: this.unsetConfirmNext,
}
}
requiredQuestionAlertOptions = () => {
return {
title: t('Required Question'),
text: t('An answer is required before proceeding.'),
continueText: t('Ok'),
hideCancelButton: true,
}
}
handleNextQuestionClick = () => {
const {
allowBacktracking,
currentQuestionIsLast,
currentSessionItemPosition,
quizSessionId,
itemId,
userResponse,
largeFormatResponse,
screenreaderNotification,
nextQuestion,
onSubmitQuizSession,
withConfirm,
isPassage,
isRequiredItem,
isSurvey,
} = this.props
if (!allowBacktracking && !currentQuestionIsLast) {
screenreaderNotification(t('Question submitted'))
} else if (!currentQuestionIsLast) {
screenreaderNotification(t('Moving to next question'))
}
const submitCb = allowBacktracking ? this.submitQuiz : onSubmitQuizSession
const hasResponded = Object.keys(userResponse).length > 0
const goNextQuestion = () => {
nextQuestion(
quizSessionId,
allowBacktracking,
currentQuestionIsLast,
currentSessionItemPosition,
itemId,
userResponse,
largeFormatResponse,
submitCb,
isPassage,
)
}
if (isPassage) {
goNextQuestion()
return
}
if (isSurvey && isRequiredItem && !hasResponded && !allowBacktracking) {
withConfirm(() => {}, this.requiredQuestionAlertOptions())
return
}
if (!hasResponded && !allowBacktracking && this.confirmNext()) {
withConfirm(goNextQuestion, this.withConfirmOptions())
return
}
goNextQuestion()
}
renderWithOneQuestionAtATime() {
const {currentQuestionIsLast, disabled} = this.props
const text = currentQuestionIsLast ? t('Submit') : t('Next')
const screenreaderText = currentQuestionIsLast ? t('Submit') : t('Next Question')
const btnType = currentQuestionIsLast ? 'primary' : 'primary-inverse'
if (this.props.iceTopNavBarEnabled && !this.props.isFooter) {
const buttonProps = {
id: 'TakeButton',
onClick: this.handleNextQuestionClick,
disabled: disabled,
'data-automation': 'sdk-oqaat-next-or-submit-button',
...this.props.styles.TopNavBarItemProps,
}
if (currentQuestionIsLast) {
return <TopNavBar.Item {...buttonProps}>{text}</TopNavBar.Item>
}
return <SecondaryNavBarButton {...buttonProps}>{text}</SecondaryNavBarButton>
}
return (
<Button
onClick={this.handleNextQuestionClick}
color={btnType}
disabled={disabled}
data-automation="sdk-oqaat-next-or-submit-button"
>
<ScreenReaderContent>{screenreaderText}</ScreenReaderContent>
<PresentationContent>{text}</PresentationContent>
</Button>
)
}
renderWithMultipleQuestionsAtATime() {
const {disabled} = this.props
if (this.props.iceTopNavBarEnabled && !this.props.isFooter) {
return (
<TopNavBar.Item
id="TakeButton"
onClick={this.submitQuiz}
disabled={disabled || !this.props.quizSessionId}
data-automation="sdk-submit-button"
{...this.props.styles.TopNavBarItemProps}
>
{t('Submit')}
</TopNavBar.Item>
)
}
return (
<Button
onClick={this.submitQuiz}
color="primary"
data-automation="sdk-submit-button"
disabled={disabled || !this.props.quizSessionId}
>
{t('Submit')}
</Button>
)
}
render() {
if (!this.props.styles.showTakeButton && !this.props.isFooter) {
return null
}
if (this.props.isOneQuestionAtATime) {
return this.renderWithOneQuestionAtATime()
}
return this.renderWithMultipleQuestionsAtATime()
}
}
export const TakeButton = withStyleOverrides(generateStyle, null)(BaseTakeButton)
export default TakeButton