Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added multiline attribute to string input element #9438

Open
wants to merge 36 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
099cf70
Added multiline attribute to string input function
eliotwrobson Feb 19, 2024
beb6bd2
Updated example question
eliotwrobson Feb 19, 2024
f17253a
Comments
eliotwrobson Feb 19, 2024
d4be415
Merge branch 'master' into string_textinput
eliotwrobson Feb 21, 2024
6a4aab3
trying something
eliotwrobson Feb 21, 2024
ebc8929
Merge branch 'string_textinput' of https://github.com/eliotwrobson/Pr…
eliotwrobson Feb 21, 2024
6136e8e
Revised example question
eliotwrobson Feb 21, 2024
0eef7c6
Updated newline example
eliotwrobson Feb 22, 2024
bea95e4
Updated panel display
eliotwrobson Feb 22, 2024
6def210
Merge branch 'master' into string_textinput
eliotwrobson Feb 22, 2024
6d09f46
Merge branch 'master' into string_textinput
eliotwrobson Apr 2, 2024
6fbfa86
Initial changes
eliotwrobson Apr 2, 2024
30b25bd
default changes
eliotwrobson Apr 2, 2024
561a2cd
Update docs/elements.md
eliotwrobson Apr 2, 2024
5727541
Python changes
eliotwrobson Apr 2, 2024
19e04f6
Question changes
eliotwrobson Apr 2, 2024
c72d8a0
stuff
eliotwrobson Apr 2, 2024
cb9f1cd
format
eliotwrobson Apr 2, 2024
1ac994a
Merge branch 'master' into string_textinput
eliotwrobson Apr 3, 2024
d1d1992
Added escape unicode option
eliotwrobson Apr 6, 2024
ab75b7b
Merge branch 'master' into string_textinput
eliotwrobson Apr 6, 2024
a28fd22
types
eliotwrobson Apr 6, 2024
7318784
Added multiline transform
eliotwrobson Apr 9, 2024
1e734b6
Initial change, still seeing some things not printing but a good star…
eliotwrobson Apr 12, 2024
15490d1
Logic + display for new behavior
eliotwrobson Apr 13, 2024
8a9c2ee
format
eliotwrobson Apr 13, 2024
c5e2d64
Update docs/elements.md
eliotwrobson Apr 13, 2024
0402f66
removed attribute
eliotwrobson Apr 13, 2024
6e4e57b
Merge branch 'string_textinput' of https://github.com/eliotwrobson/Pr…
eliotwrobson Apr 13, 2024
9032c6d
Updated docs about newline behavior
eliotwrobson Apr 13, 2024
55a1a49
Updated example question and other behavior
eliotwrobson Apr 13, 2024
6daf5f1
Format
eliotwrobson Apr 13, 2024
bee5f79
Merge branch 'master' into string_textinput
eliotwrobson Apr 13, 2024
6bb8fe8
Removed line break
eliotwrobson Apr 13, 2024
a60af8e
Merge branch 'string_textinput' of https://github.com/eliotwrobson/Pr…
eliotwrobson Apr 13, 2024
46cdfeb
Merge branch 'master' into string_textinput
eliotwrobson Apr 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@
container: 'body',
template: '<div class="popover pl-string-input-popover" role="tooltip"><div class="arrow"></div><h3 class="popover-header"></h3><div class="popover-body"></div></div>',
});
document.querySelectorAll('.multiline-input').forEach((input) => {
input.addEventListener('input', function () {
// Adjusts the height based on the feedback content. If the feedback changes, the height
// changes as well. This is done by resetting the height (so the scrollHeight is computed
// based on the minimum height) and then using the scrollHeight plus padding as the new height.
this.style.height = '';
if (this.scrollHeight) {
const style = window.getComputedStyle(this);
this.style.height =
this.scrollHeight + parseFloat(style.paddingTop) + parseFloat(style.paddingBottom) + 'px';
}
});
input.dispatchEvent(new Event('input'));
});
});
</script>

Expand All @@ -16,20 +30,37 @@
<span class="input-group-text" id="pl-string-input-{{uuid}}-label">{{{label}}}</span>
</span>
{{/label}}
{{^multiline}}
<input
{{/multiline}}
{{#multiline}}
<textarea
wrap="soft"
{{/multiline}}
name="{{name}}"
type="text"
inputmode="text"
class="form-control pl-string-input-input"
size="{{size}}"
class="form-control pl-string-input-input {{#multiline}}multiline-input{{/multiline}}"
{{^multiline}}
size="{{size}}"
{{#raw_submitted_answer}}value="{{raw_submitted_answer}}"{{/raw_submitted_answer}}
{{/multiline}}
{{#multiline}}
cols="{{size}}"
rows="2"
{{/multiline}}
autocomplete="off"
{{^editable}}disabled{{/editable}}
{{#raw_submitted_answer}}value="{{raw_submitted_answer}}"{{/raw_submitted_answer}}
aria-describedby="pl-symbolic-input-{{uuid}}-label pl-symbolic-input-{{uuid}}-suffix"
aria-describedby="pl-string-input-{{uuid}}-label pl-string-input-{{uuid}}-suffix"
placeholder="{{placeholder}}"
/>
{{^multiline}}
>
{{/multiline}}
{{#multiline}}
>{{raw_submitted_answer}}</textarea>
{{/multiline}}
<span class="input-group-append">
{{#suffix}}<span class="input-group-text" id="pl-symbolic-input-{{uuid}}-suffix">{{suffix}}</span>{{/suffix}}
{{#suffix}}<span class="input-group-text" id="pl-string-input-{{uuid}}-suffix">{{suffix}}</span>{{/suffix}}

{{#show_info}}
<a role="button" class="btn btn-light border d-flex align-items-center" data-toggle="popover" data-html="true" title="String" data-content="{{info}}" data-placement="auto" data-trigger="focus" tabindex="0">
Expand Down
10 changes: 9 additions & 1 deletion apps/prairielearn/elements/pl-string-input/pl-string-input.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import random
import re
from enum import Enum
from typing import Any

Expand Down Expand Up @@ -27,6 +28,7 @@ class DisplayType(Enum):
SHOW_HELP_TEXT_DEFAULT = True
SHOW_SCORE_DEFAULT = True
NORMALIZE_TO_ASCII_DEFAULT = False
MULTILINE_DEFAULT = False

STRING_INPUT_MUSTACHE_TEMPLATE_NAME = "pl-string-input.mustache"

Expand All @@ -49,6 +51,7 @@ def prepare(element_html: str, data: pl.QuestionData) -> None:
"show-help-text",
"normalize-to-ascii",
"show-score",
"multiline",
]
pl.check_attribs(element, required_attribs, optional_attribs)

Expand Down Expand Up @@ -130,6 +133,7 @@ def render(element_html: str, data: pl.QuestionData) -> str:
display.value: True,
"raw_submitted_answer": raw_submitted_answer,
"parse_error": parse_error,
"multiline": pl.get_boolean_attrib(element, "multiline", MULTILINE_DEFAULT),
}

if show_score and score is not None:
Expand Down Expand Up @@ -180,6 +184,8 @@ def render(element_html: str, data: pl.QuestionData) -> str:
if a_tru is None:
return ""

a_tru = pl.escape_unicode_string(a_tru)

html_params = {
"answer": True,
"label": label,
Expand All @@ -203,7 +209,6 @@ def parse(element_html: str, data: pl.QuestionData) -> None:
remove_spaces = pl.get_boolean_attrib(
element, "remove-spaces", REMOVE_SPACES_DEFAULT
)

remove_leading_trailing = pl.get_boolean_attrib(
element, "remove-leading-trailing", REMOVE_LEADING_TRAILING_DEFAULT
)
Expand All @@ -227,6 +232,9 @@ def parse(element_html: str, data: pl.QuestionData) -> None:
if remove_spaces:
a_sub = "".join(a_sub.split())

# Always simplify multiline characters (if they still exist)
a_sub = re.sub("\r*\n", "\n", a_sub)
eliotwrobson marked this conversation as resolved.
Show resolved Hide resolved

if not a_sub and not allow_blank:
data["format_errors"][
name
Expand Down
7 changes: 6 additions & 1 deletion docs/elements.md
Original file line number Diff line number Diff line change
Expand Up @@ -998,8 +998,13 @@ def generate(data):
| `ignore-case` | boolean | false | Whether or not to enforce case sensitivity (e.g. "hello" != "HELLO"). |
| `normalize-to-ascii` | boolean | false | Whether non-English characters (accents, non-latin alphabets, fancy quotes) should be normalized to equivalent English characters before submitting the file for grading. |
| `placeholder` | text | None | Hint displayed inside the input box describing the expected type of input. |
| `size` | integer | 35 | Size of the input box. |
| `size` | integer | 35 | Width of the input box. |
| `show-help-text` | boolean | true | Show the question mark at the end of the input displaying required input parameters. |
| `multiline` | boolean | false | Whether or not to to allow the input to include line breaks. |

#### Details

The student answer will use a single `\n` as a newline character, even if `multiline` is not enabled. To avoid issues with newlines, they can be ignored by enabling `remove-spaces`. Keep in mind that longer strings may not display cleanly on the submitted or correct answer panels, so it may be best to hide these panels if students are submitting longer strings.
eliotwrobson marked this conversation as resolved.
Show resolved Hide resolved
eliotwrobson marked this conversation as resolved.
Show resolved Hide resolved

#### Example implementations

Expand Down
207 changes: 107 additions & 100 deletions exampleCourse/questions/element/stringInput/question.html
Original file line number Diff line number Diff line change
@@ -1,114 +1,121 @@
<pl-question-panel>
<p> 1) For the following python code snippet, enter below the resulting string:</p>

<pl-card>
<pl-question-panel>
<p> For the following python code snippet, enter below the resulting string:</p>
<pl-code language="python">
a = "{{params.stringname}}"*{{params.a}}
</pl-code>

</pl-question-panel>

<p>
<pl-string-input answers-name="ans1" label="a = ">
</pl-string-input>
</p>

<pl-question-panel>
<p> 2) For the following python code snippet, what is d = b + c?</p>
<pl-code language="python">
b = "{{params.b}}"
c = "{{params.c}}"
</pl-code>
</pl-question-panel>

<p>
<pl-string-input answers-name="ans2" label="b + c = ">
</pl-string-input>
</p>

<pl-question-panel>
<p>3) Enter the string below, however include trailing and leading blanks.
When using the attribute <code>remove-leading-trailing="true"</code>, the answer will
still be evaluated as correct. We also set <code>show-score="false"</code> to hide the
score badge. </p>
<pl-code language="python">
"{{params.d}}"
</pl-code>
</pl-question-panel>

<p>
<pl-string-input answers-name="ans3" remove-leading-trailing="true" show-score="false">
</pl-string-input>
</p>

<pl-question-panel>
<p> 4) Enter the string below, however include blank spaces between some of the numbers.
When using the attribute <code>remove-spaces="true"</code>, the answer will
still be evaluated as correct. This example also uses the attribute
<code>placeholder="string"</code>,
to add a placeholder indicating the expected input is of type "string".</p>
</pl-question-panel>
<pl-string-input answers-name="ans1" label="a = ">
</pl-string-input>
</pl-card>

<pl-card>
<pl-question-panel>
<p> For the following python code snippet, what is d = b + c?</p>
<pl-code language="python">
"{{params.d}}"
b = "{{params.b}}"
c = "{{params.c}}"
</pl-code>
</pl-question-panel>

<p>
<pl-string-input answers-name="ans4" remove-spaces="true" placeholder="string">
</pl-string-input>
</p>


<pl-question-panel>
<p> 5) The attribute <code>allow-blank="true"</code> allows users to submit blank answers, without
receiving the <code>Invalid</code> error. Leave the box below empty, and notice
that you will not receive the <code>Invalid</code> feedback (and hence the question is graded). </p>
</pl-question-panel>

<p>
<pl-string-input answers-name="ans5" label="blank input box = " allow-blank="true">
</pl-string-input>
</p>

<pl-question-panel>
<p> 6) To allow for case insensitivity, use <code>ignore-case="true"</code>.
This will autoconvert both the student answer and the correct answer to lower case. Entering the string below with any choice of uppercase and lowercase letters will yield the correct answer as a result. </p>

<pl-code language="python">
"{{params.e}}"
</pl-code>
</pl-question-panel>

<p>
<pl-string-input answers-name="ans6" label="Input box is case insensitive = " ignore-case="true">
</pl-string-input>
</p>

<pl-question-panel>
</pl-question-panel>
<pl-string-input answers-name="ans2" label="b + c = ">
</pl-string-input>
</pl-card>

<pl-card>
<pl-question-panel>
<p> Enter the string below, however include trailing and leading blanks.
When using the attribute <code>remove-leading-trailing="true"</code>, the answer will
still be evaluated as correct. We also set <code>show-score="false"</code> to hide the
score badge. </p>
<pl-code language="python">"{{params.d}}"</pl-code>
</pl-question-panel>
<pl-string-input answers-name="ans3" remove-leading-trailing="true" show-score="false">
</pl-string-input>
</pl-card>

<pl-card>
<pl-question-panel>
<p>Enter the string below, however include blank spaces between some of the numbers.
When using the attribute <code>remove-spaces="true"</code>, the answer will
still be evaluated as correct. This example also uses the attribute
<code>placeholder="string"</code>,
to add a placeholder indicating the expected input is of type "string".
</p>
<pl-code language="python">"{{params.d}}"</pl-code>
</pl-question-panel>
<pl-string-input answers-name="ans4" remove-spaces="true" placeholder="string">
</pl-string-input>
</pl-card>

<pl-card>
<pl-question-panel>
<p>The attribute <code>allow-blank="true"</code> allows users to submit blank answers, without
receiving the <code>Invalid</code> error. Leave the box below empty, and notice
that you will not receive the <code>Invalid</code> feedback (and hence the question is graded). </p>
</pl-question-panel>

<pl-string-input answers-name="ans5" label="blank input box = " allow-blank="true">
</pl-string-input>
</pl-card>

<pl-card>
<pl-question-panel>
<p> To allow for case insensitivity, use <code>ignore-case="true"</code>.
This will autoconvert both the student answer and the correct answer to lower case. Entering the string below with
any choice of uppercase and lowercase letters will yield the correct answer as a result. </p>

<pl-code language="python">"{{params.e}}"</pl-code>
</pl-question-panel>

<pl-string-input answers-name="ans6" label="Input box is case insensitive = " ignore-case="true">
</pl-string-input>
</pl-card>

<pl-card>
<pl-question-panel>
<p>
7) The size of the input box can be set with the attribute <code>size</code>, and the attribute <code>show-help-text="false"</code> removes the button for the help popup. This box below has <code>size=5</code> instead of the default <code>size=35</code>.
The size of the input box can be set with the attribute <code>size</code>, and the attribute
<code>show-help-text="false"</code> removes the button for the help popup. This box below has <code>size=5</code>
instead of the default <code>size=35</code>.
</p>
<pl-code language="python">"{{params.f}}"</pl-code>
</pl-question-panel>

<pl-code language="python">
"{{params.f}}"
</pl-code>
</pl-question-panel>

<p>
<pl-string-input answers-name="ans7" label="Small box = " show-help-text="false" size="5"></pl-string-input>
</p>
<pl-string-input answers-name="ans7" label="Small box = " show-help-text="false" size="5"></pl-string-input>
</pl-card>

<pl-question-panel>
<pl-card>
<pl-question-panel>
<p>
8) For students using non-latin keyboards, it is possible that non-English characters may be introduced into the box. This can cause confusion if these characters resemble a latin character (e.g., the cyrillic character <strong>&#x0430;</strong> looks a lot like the latin character <strong>a</strong>). Similarly, students may copy from text editors and paste into the answer box, causing characters like fancy quotes to be introduced. The attribute <code>normalize-to-ascii="true"</code> converts many Unicode characters to the ASCII character that mostly resembles it.
For students using non-latin keyboards, it is possible that non-English characters may be introduced into the
box. This can cause confusion if these characters resemble a latin character (e.g., the cyrillic character
<strong>&#x0430;</strong> looks a lot like the latin character <strong>a</strong>). Similarly, students may copy
from text editors and paste into the answer box, causing characters like fancy quotes to be introduced. The
attribute <code>normalize-to-ascii="true"</code> converts many Unicode characters to the ASCII character that
mostly
resembles it.
</p>
<p>
The box below contains an input using a Greek Epsilon, a Cyrillic ie, and fancy quotes. Copying this text into the answer box below should result in the appropriate text (<code>"Ee"</code>) being accepted.
The box below contains an input using a Greek Epsilon, a Cyrillic ie, and fancy quotes. Copying this text into the
answer box below should result in the appropriate text (<code>"Ee"</code>) being accepted.
</p>
<pl-code copy-code-button="true">{{{params.g}}}</pl-code>
</pl-question-panel>
<pl-string-input answers-name="ans8" label="Weird text = " suffix="?" normalize-to-ascii="true"></pl-string-input>
</pl-card>

<div>
{{{params.g}}}
</div>
</pl-question-panel>

<p>
<pl-string-input answers-name="ans8" label="Weird text = " suffix="?" normalize-to-ascii="true"></pl-string-input>
</p>
<pl-card>
<pl-question-panel>
<p>
The input box can also allow for multiline input, done by setting <code>multiline="true"</code>. For example,
this is useful for cases where students are asked for the output of a program. Note that the multiline input
converts each newline character to a single <code>\n</code>, so keep this in mind when setting the correct answer.
To avoid issues with newlines, they can be ignored completely by setting <code>remove-spaces="true"</code>.
</p>
<p>
Run the Python code below and submit the output into the answer box.
</p>
<pl-code copy-code-button="true" language="python">print("Hello\nWorld!")</pl-code>
</pl-question-panel>
<pl-string-input answers-name="ans9" label="Program output " multiline="true"></pl-string-input>
</pl-card>
1 change: 1 addition & 0 deletions exampleCourse/questions/element/stringInput/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ def generate(data):
data["correct_answers"]["ans6"] = e
data["correct_answers"]["ans7"] = f
data["correct_answers"]["ans8"] = '"Ee"'
data["correct_answers"]["ans9"] = "Hello\nWorld!\n"
Loading