UNPKG

@instructure/quiz-taking

Version:
254 lines (220 loc) • 7.41 kB
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