Commit f509716d by gradyward

Merge branch 'will/js-field-validation' of https://github.com/edx/edx-ora2 into…

Merge branch 'will/js-field-validation' of https://github.com/edx/edx-ora2 into grady/js-validation-alert-broadening

Conflicts:
	openassessment/xblock/static/js/openassessment-studio.min.js
	openassessment/xblock/static/js/src/studio/oa_container_item.js
	openassessment/xblock/static/js/src/studio/oa_edit.js
	openassessment/xblock/static/js/src/studio/oa_edit_assessment.js
	openassessment/xblock/static/js/src/studio/oa_edit_fields.js
	openassessment/xblock/static/js/src/studio/oa_edit_rubric.js
	openassessment/xblock/static/js/src/studio/oa_edit_settings.js
	openassessment/xblock/static/js/src/studio/oa_edit_validation_alert.js
	scripts/render_templates.py
parents 58bcabc7 a5921b62
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
class="openassessment_criterion_option_points input setting-input" class="openassessment_criterion_option_points input setting-input"
type="number" type="number"
value="{{ option_points }}" value="{{ option_points }}"
min="0" min="0" max="999"
> >
</label> </label>
</div> </div>
...@@ -47,4 +47,4 @@ ...@@ -47,4 +47,4 @@
</ul> </ul>
</div> </div>
</li> </li>
{% endspaceless %} {% endspaceless %}
\ No newline at end of file
...@@ -74,7 +74,14 @@ OpenAssessment.RubricOption.prototype = { ...@@ -74,7 +74,14 @@ OpenAssessment.RubricOption.prototype = {
}, },
/** /**
TODO Get or set the label of the option.
Args:
label (string, optional): If provided, set the label to this string.
Returns:
string
**/ **/
label: function(label) { label: function(label) {
var sel = $('.openassessment_criterion_option_label', this.element); var sel = $('.openassessment_criterion_option_label', this.element);
...@@ -82,7 +89,14 @@ OpenAssessment.RubricOption.prototype = { ...@@ -82,7 +89,14 @@ OpenAssessment.RubricOption.prototype = {
}, },
/** /**
TODO Get or set the point value of the option.
Args:
points (int, optional): If provided, set the point value of the option.
Returns:
int
**/ **/
points: function(points) { points: function(points) {
var sel = $('.openassessment_criterion_option_points', this.element); var sel = $('.openassessment_criterion_option_points', this.element);
...@@ -90,7 +104,14 @@ OpenAssessment.RubricOption.prototype = { ...@@ -90,7 +104,14 @@ OpenAssessment.RubricOption.prototype = {
}, },
/** /**
TODO Get or set the explanation for the option.
Args:
explanation (string, optional): If provided, set the explanation to this string.
Returns:
string
**/ **/
explanation: function(explanation) { explanation: function(explanation) {
var sel = $('.openassessment_criterion_option_explanation', this.element); var sel = $('.openassessment_criterion_option_explanation', this.element);
...@@ -168,7 +189,11 @@ OpenAssessment.RubricOption.prototype = { ...@@ -168,7 +189,11 @@ OpenAssessment.RubricOption.prototype = {
}, },
/** /**
TODO Mark validation errors.
Returns:
Boolean indicating whether the option is valid.
**/ **/
validate: function() { validate: function() {
var pointString = $(".openassessment_criterion_option_points", this.element).val(); var pointString = $(".openassessment_criterion_option_points", this.element).val();
...@@ -182,7 +207,12 @@ OpenAssessment.RubricOption.prototype = { ...@@ -182,7 +207,12 @@ OpenAssessment.RubricOption.prototype = {
}, },
/** /**
TODO Return a list of validation errors visible in the UI.
Mainly useful for testing.
Returns:
list of string
**/ **/
validationErrors: function() { validationErrors: function() {
var sel = $(".openassessment_criterion_option_points", this.element); var sel = $(".openassessment_criterion_option_points", this.element);
...@@ -191,7 +221,7 @@ OpenAssessment.RubricOption.prototype = { ...@@ -191,7 +221,7 @@ OpenAssessment.RubricOption.prototype = {
}, },
/** /**
TODO Clear all validation errors from the UI.
**/ **/
clearValidationErrors: function() { clearValidationErrors: function() {
$(".openassessment_criterion_option_points", this.element) $(".openassessment_criterion_option_points", this.element)
...@@ -268,7 +298,14 @@ OpenAssessment.RubricCriterion.prototype = { ...@@ -268,7 +298,14 @@ OpenAssessment.RubricCriterion.prototype = {
}, },
/** /**
TODO Get or set the label of the criterion.
Args:
label (string, optional): If provided, set the label to this string.
Returns:
string
**/ **/
label: function(label) { label: function(label) {
var sel = $('.openassessment_criterion_label', this.element); var sel = $('.openassessment_criterion_label', this.element);
...@@ -276,7 +313,14 @@ OpenAssessment.RubricCriterion.prototype = { ...@@ -276,7 +313,14 @@ OpenAssessment.RubricCriterion.prototype = {
}, },
/** /**
TODO Get or set the prompt of the criterion.
Args:
prompt (string, optional): If provided, set the prompt to this string.
Returns:
string
**/ **/
prompt: function(prompt) { prompt: function(prompt) {
var sel = $('.openassessment_criterion_prompt', this.element); var sel = $('.openassessment_criterion_prompt', this.element);
...@@ -284,11 +328,15 @@ OpenAssessment.RubricCriterion.prototype = { ...@@ -284,11 +328,15 @@ OpenAssessment.RubricCriterion.prototype = {
}, },
/** /**
TODO Get the feedback value for the criterion.
This is one of: "disabled", "optional", or "required".
Returns:
string
**/ **/
feedback: function(feedback) { feedback: function() {
var sel = $('.openassessment_criterion_feedback', this.element); return $('.openassessment_criterion_feedback', this.element).val();
return OpenAssessment.Fields.stringField(sel, feedback);
}, },
/** /**
...@@ -336,7 +384,11 @@ OpenAssessment.RubricCriterion.prototype = { ...@@ -336,7 +384,11 @@ OpenAssessment.RubricCriterion.prototype = {
}, },
/** /**
TODO Mark validation errors.
Returns:
Boolean indicating whether the criterion is valid.
**/ **/
validate: function() { validate: function() {
var isValid = true; var isValid = true;
...@@ -346,8 +398,13 @@ OpenAssessment.RubricCriterion.prototype = { ...@@ -346,8 +398,13 @@ OpenAssessment.RubricCriterion.prototype = {
return isValid; return isValid;
}, },
/** /**
TODO Return a list of validation errors visible in the UI.
Mainly useful for testing.
Returns:
list of string
**/ **/
validationErrors: function() { validationErrors: function() {
var errors = []; var errors = [];
...@@ -358,7 +415,7 @@ OpenAssessment.RubricCriterion.prototype = { ...@@ -358,7 +415,7 @@ OpenAssessment.RubricCriterion.prototype = {
}, },
/** /**
TODO Clear all validation errors from the UI.
**/ **/
clearValidationErrors: function() { clearValidationErrors: function() {
$.each(this.optionContainer.getAllItems(), function() { $.each(this.optionContainer.getAllItems(), function() {
......
...@@ -125,13 +125,21 @@ OpenAssessment.StudioView.prototype = { ...@@ -125,13 +125,21 @@ OpenAssessment.StudioView.prototype = {
var view = this; var view = this;
this.saveTabState(); this.saveTabState();
// Perform client-side validation // Perform client-side validation:
// TODO -- more explanation // * Hide the validation alert
// * Clear errors from any field marked as invalid.
// * Mark invalid fields in the UI.
// * If there are any validation errors, show an alert.
//
// The `validate()` method calls `validate()` on any subviews,
// so that each subview has the opportunity to validate
// its fields.
this.validationAlert.hide(); this.validationAlert.hide();
this.clearValidationErrors(); this.clearValidationErrors();
if (!this.validate()) { if (!this.validate()) {
this.validationAlert.setMessage( this.validationAlert.setMessage(
gettext("Validation errors! [TODO Sylvia please help!]") gettext("Validation Errors"),
gettext("Some fields are not valid. Please update the fields.")
).show(); ).show();
} }
else { else {
...@@ -215,14 +223,23 @@ OpenAssessment.StudioView.prototype = { ...@@ -215,14 +223,23 @@ OpenAssessment.StudioView.prototype = {
}, },
/** /**
TODO Mark validation errors.
Returns:
Boolean indicating whether the view is valid.
**/ **/
validate: function() { validate: function() {
return this.settingsView.validate() && this.rubricView.validate(); return this.settingsView.validate() && this.rubricView.validate();
}, },
/** /**
TODO Return a list of validation errors visible in the UI.
Mainly useful for testing.
Returns:
list of string
**/ **/
validationErrors: function() { validationErrors: function() {
return this.settingsView.validationErrors().concat( return this.settingsView.validationErrors().concat(
...@@ -231,7 +248,7 @@ OpenAssessment.StudioView.prototype = { ...@@ -231,7 +248,7 @@ OpenAssessment.StudioView.prototype = {
}, },
/** /**
TODO Clear all validation errors from the UI.
**/ **/
clearValidationErrors: function() { clearValidationErrors: function() {
this.settingsView.clearValidationErrors(); this.settingsView.clearValidationErrors();
......
...@@ -140,14 +140,23 @@ OpenAssessment.EditPeerAssessmentView.prototype = { ...@@ -140,14 +140,23 @@ OpenAssessment.EditPeerAssessmentView.prototype = {
}, },
/** /**
TODO Mark validation errors.
Returns:
Boolean indicating whether the view is valid.
**/ **/
validate: function() { validate: function() {
return this.startDatetimeControl.validate() && this.dueDatetimeControl.validate(); return this.startDatetimeControl.validate() && this.dueDatetimeControl.validate();
}, },
/** /**
TODO Return a list of validation errors visible in the UI.
Mainly useful for testing.
Returns:
list of string
**/ **/
validationErrors: function() { validationErrors: function() {
var errors = []; var errors = [];
...@@ -161,7 +170,7 @@ OpenAssessment.EditPeerAssessmentView.prototype = { ...@@ -161,7 +170,7 @@ OpenAssessment.EditPeerAssessmentView.prototype = {
}, },
/** /**
TODO Clear all validation errors from the UI.
**/ **/
clearValidationErrors: function() { clearValidationErrors: function() {
this.startDatetimeControl.clearValidationErrors(); this.startDatetimeControl.clearValidationErrors();
...@@ -281,14 +290,23 @@ OpenAssessment.EditSelfAssessmentView.prototype = { ...@@ -281,14 +290,23 @@ OpenAssessment.EditSelfAssessmentView.prototype = {
}, },
/** /**
TODO Mark validation errors.
Returns:
Boolean indicating whether the view is valid.
**/ **/
validate: function() { validate: function() {
return this.startDatetimeControl.validate() && this.dueDatetimeControl.validate(); return this.startDatetimeControl.validate() && this.dueDatetimeControl.validate();
}, },
/** /**
TODO Return a list of validation errors visible in the UI.
Mainly useful for testing.
Returns:
list of string
**/ **/
validationErrors: function() { validationErrors: function() {
var errors = []; var errors = [];
...@@ -302,7 +320,7 @@ OpenAssessment.EditSelfAssessmentView.prototype = { ...@@ -302,7 +320,7 @@ OpenAssessment.EditSelfAssessmentView.prototype = {
}, },
/** /**
TODO Clear all validation errors from the UI.
**/ **/
clearValidationErrors: function() { clearValidationErrors: function() {
this.startDatetimeControl.clearValidationErrors(); this.startDatetimeControl.clearValidationErrors();
...@@ -400,25 +418,9 @@ OpenAssessment.EditStudentTrainingView.prototype = { ...@@ -400,25 +418,9 @@ OpenAssessment.EditStudentTrainingView.prototype = {
return $(this.element).attr('id'); return $(this.element).attr('id');
}, },
/** validate: function() { return true; },
TODO validationErrors: function() { return []; },
**/ clearValidationErrors: function() {},
validate: function() {
return true;
},
/**
TODO
**/
validationErrors: function() {
return [];
},
/**
TODO
**/
clearValidationErrors: function() {
},
}; };
/** /**
...@@ -502,23 +504,7 @@ OpenAssessment.EditExampleBasedAssessmentView.prototype = { ...@@ -502,23 +504,7 @@ OpenAssessment.EditExampleBasedAssessmentView.prototype = {
return $(this.element).attr('id'); return $(this.element).attr('id');
}, },
/** validate: function() { return true; },
TODO validationErrors: function() { return []; },
**/ clearValidationErrors: function() {},
validate: function() {
return true;
},
/**
TODO
**/
validationErrors: function() {
return [];
},
/**
TODO
**/
clearValidationErrors: function() {
},
}; };
\ No newline at end of file
...@@ -124,7 +124,11 @@ OpenAssessment.DatetimeControl.prototype = { ...@@ -124,7 +124,11 @@ OpenAssessment.DatetimeControl.prototype = {
}, },
/** /**
TODO Mark validation errors.
Returns:
Boolean indicating whether the fields are valid.
**/ **/
validate: function() { validate: function() {
var datetimeString = this.datetime(); var datetimeString = this.datetime();
...@@ -140,15 +144,20 @@ OpenAssessment.DatetimeControl.prototype = { ...@@ -140,15 +144,20 @@ OpenAssessment.DatetimeControl.prototype = {
}, },
/** /**
TODO Clear all validation errors from the UI.
**/ **/
clearValidationErrors: function() { clearValidationErrors: function() {
$(this.datePicker, this.element).removeClass("openassessment_highlighted_field"); $(this.datePicker, this.element).removeClass("openassessment_highlighted_field");
$(this.timePicker, this.element).removeClass("openassessment_highlighted_field"); $(this.timePicker, this.element).removeClass("openassessment_highlighted_field");
}, },
/** /**
TODO Return a list of validation errors visible in the UI.
Mainly useful for testing.
Returns:
list of string
**/ **/
validationErrors: function() { validationErrors: function() {
var errors = []; var errors = [];
......
...@@ -192,7 +192,11 @@ OpenAssessment.EditRubricView.prototype = { ...@@ -192,7 +192,11 @@ OpenAssessment.EditRubricView.prototype = {
}, },
/** /**
TODO Mark validation errors.
Returns:
Boolean indicating whether the view is valid.
**/ **/
validate: function() { validate: function() {
var isValid = true; var isValid = true;
...@@ -204,8 +208,13 @@ OpenAssessment.EditRubricView.prototype = { ...@@ -204,8 +208,13 @@ OpenAssessment.EditRubricView.prototype = {
return isValid; return isValid;
}, },
/** /**
TODO Return a list of validation errors visible in the UI.
Mainly useful for testing.
Returns:
list of string
**/ **/
validationErrors: function() { validationErrors: function() {
var errors = []; var errors = [];
...@@ -218,7 +227,7 @@ OpenAssessment.EditRubricView.prototype = { ...@@ -218,7 +227,7 @@ OpenAssessment.EditRubricView.prototype = {
}, },
/** /**
TODO Clear all validation errors from the UI.
**/ **/
clearValidationErrors: function() { clearValidationErrors: function() {
$.each(this.getAllCriteria(), function() { $.each(this.getAllCriteria(), function() {
......
...@@ -200,16 +200,21 @@ OpenAssessment.EditSettingsView.prototype = { ...@@ -200,16 +200,21 @@ OpenAssessment.EditSettingsView.prototype = {
}, },
/** /**
TODO Mark validation errors.
Returns:
Boolean indicating whether the view is valid.
**/ **/
validate: function() { validate: function() {
var isValid = true; // Validate the start and due datetime controls
var isValid = (
isValid = isValid && this.startDatetimeControl.validate(); this.startDatetimeControl.validate() &&
isValid = isValid && this.dueDatetimeControl.validate(); this.dueDatetimeControl.validate()
);
// Validate each of the assessment views
$.each(this.assessmentViews, function() { $.each(this.assessmentViews, function() {
// TODO -- explain why we need to do it this way
isValid = (isValid && this.validate()); isValid = (isValid && this.validate());
}); });
...@@ -217,7 +222,12 @@ OpenAssessment.EditSettingsView.prototype = { ...@@ -217,7 +222,12 @@ OpenAssessment.EditSettingsView.prototype = {
}, },
/** /**
TODO Return a list of validation errors visible in the UI.
Mainly useful for testing.
Returns:
list of string
**/ **/
validationErrors: function() { validationErrors: function() {
var errors = []; var errors = [];
...@@ -236,7 +246,7 @@ OpenAssessment.EditSettingsView.prototype = { ...@@ -236,7 +246,7 @@ OpenAssessment.EditSettingsView.prototype = {
}, },
/** /**
TODO Clear all validation errors from the UI.
**/ **/
clearValidationErrors: function() { clearValidationErrors: function() {
this.startDatetimeControl.clearValidationErrors(); this.startDatetimeControl.clearValidationErrors();
......
...@@ -6,6 +6,7 @@ Returns: ...@@ -6,6 +6,7 @@ Returns:
Openassessment.ValidationAlert Openassessment.ValidationAlert
*/ */
OpenAssessment.ValidationAlert = function() { OpenAssessment.ValidationAlert = function() {
this.element = $('#openassessment_validation_alert'); this.element = $('#openassessment_validation_alert');
this.editorElement = $(this.element).parent(); this.editorElement = $(this.element).parent();
this.title = $(".openassessment_alert_title", this.element); this.title = $(".openassessment_alert_title", this.element);
...@@ -15,7 +16,7 @@ OpenAssessment.ValidationAlert = function() { ...@@ -15,7 +16,7 @@ OpenAssessment.ValidationAlert = function() {
OpenAssessment.ValidationAlert.prototype = { OpenAssessment.ValidationAlert.prototype = {
/** /**
TODO Install the event handlers for the alert.
**/ **/
installEventHandlers: function() { installEventHandlers: function() {
var alert = this; var alert = this;
...@@ -31,13 +32,14 @@ OpenAssessment.ValidationAlert.prototype = { ...@@ -31,13 +32,14 @@ OpenAssessment.ValidationAlert.prototype = {
Hides the alert. Hides the alert.
Returns: Returns:
TODO OpenAssessment.ValidationAlert
*/ */
hide: function() { hide: function() {
// Finds the height of all other elements in the editor_and_tabs (the Header) and sets the height // Finds the height of all other elements in the editor_and_tabs (the Header) and sets the height
// of the editing area to be 100% of that element minus those constraints. // of the editing area to be 100% of that element minus those constraints.
var headerHeight = $('#openassessment_editor_header', this.editorElement).outerHeight(); var headerHeight = $('#openassessment_editor_header', this.editorElement).outerHeight();
this.element.addClass('is--hidden'); this.element.addClass('is--hidden');
var styles = { var styles = {
'height': 'Calc(100% - ' + headerHeight + 'px)', 'height': 'Calc(100% - ' + headerHeight + 'px)',
'border-top-right-radius': '3px', 'border-top-right-radius': '3px',
...@@ -47,6 +49,7 @@ OpenAssessment.ValidationAlert.prototype = { ...@@ -47,6 +49,7 @@ OpenAssessment.ValidationAlert.prototype = {
$('.oa_editor_content_wrapper', this.editorElement).each( function () { $('.oa_editor_content_wrapper', this.editorElement).each( function () {
$(this).css(styles); $(this).css(styles);
}); });
return this; return this;
}, },
...@@ -54,7 +57,7 @@ OpenAssessment.ValidationAlert.prototype = { ...@@ -54,7 +57,7 @@ OpenAssessment.ValidationAlert.prototype = {
Displays the alert. Displays the alert.
Returns: Returns:
TODO OpenAssessment.ValidationAlert
*/ */
show : function() { show : function() {
this.element.removeClass('is--hidden'); this.element.removeClass('is--hidden');
...@@ -71,6 +74,7 @@ OpenAssessment.ValidationAlert.prototype = { ...@@ -71,6 +74,7 @@ OpenAssessment.ValidationAlert.prototype = {
$('.oa_editor_content_wrapper', this.editorElement).each( function () { $('.oa_editor_content_wrapper', this.editorElement).each( function () {
$(this).css(styles); $(this).css(styles);
}); });
return this; return this;
}, },
...@@ -83,7 +87,7 @@ OpenAssessment.ValidationAlert.prototype = { ...@@ -83,7 +87,7 @@ OpenAssessment.ValidationAlert.prototype = {
newMessage (str): the new text that the message's body will contain newMessage (str): the new text that the message's body will contain
Returns: Returns:
TODO OpenAssessment.ValidationAlert
*/ */
setMessage: function(newTitle, newMessage) { setMessage: function(newTitle, newMessage) {
this.title.text(newTitle); this.title.text(newTitle);
......
...@@ -39,11 +39,29 @@ from django.template.loader import get_template ...@@ -39,11 +39,29 @@ from django.template.loader import get_template
USAGE = u"{prog} TEMPLATE_DESC" USAGE = u"{prog} TEMPLATE_DESC"
ISO_DATE_REGEX = re.compile("^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$") DATETIME_REGEX = re.compile("^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$")
def parse_dates(context): def parse_dates(context):
""" """
TODO Transform datetime strings into Python datetime objects.
JSON does not provide a standard way to serialize datetime objects,
but some of the templates expect that the context contains
Python datetime objects.
This (somewhat hacky) solution recursively searches the context
for formatted datetime strings of the form "2014-01-02T12:34"
and converts them to Python datetime objects with the timezone
set to UTC.
Args:
context (JSON-serializable): The context (or part of the context)
that will be passed to the template. Dictionaries and lists
will be recursively searched and transformed.
Returns:
JSON-serializable of the same type as the `context` argument.
""" """
if isinstance(context, dict): if isinstance(context, dict):
return { return {
...@@ -56,7 +74,7 @@ def parse_dates(context): ...@@ -56,7 +74,7 @@ def parse_dates(context):
for item in context for item in context
] ]
elif isinstance(context, basestring): elif isinstance(context, basestring):
if ISO_DATE_REGEX.match(context) is not None: if DATETIME_REGEX.match(context) is not None:
return dateutil.parser.parse(context).replace(tzinfo=pytz.utc) return dateutil.parser.parse(context).replace(tzinfo=pytz.utc)
return context return context
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment