Commit 7b793ad4 by gradyward Committed by Will Daly

A first pass at containers.

Restructred the container to be heavily reliant on DOM structure,
largely ignoring all unique identifiers and shrinking the implicit size
of the Container Item classes

Completed the initial pass of the Container Item Class.

Added tests and fixed major and minor issues.

Conflicts:
	openassessment/xblock/static/js/openassessment.min.js
parent 85323091
This source diff could not be displayed because it is too large. You can view the blob instead.
/**
Tests for the Openassessment Container Object.
**/
describe("OpenAssessment.Container", function () {
var container = null;
beforeEach(function () {
jasmine.getFixtures().fixturesPath = 'base/fixtures';
loadFixtures('oa_edit.html');
container = new OpenAssessment.Container(
$('#openassessment_criterion_list'),
{
'openassessment_criterion': OpenAssessment.RubricCriterion
}
)
});
it("adds a criterion", function () {
var previousSize = $('.openassessment_criterion').length;
container.add('openassessment_criterion');
var newSize = $('.openassessment_criterion').length;
expect(newSize).toEqual(previousSize + 1);
});
it("removes a criterion", function () {
container.add('openassessment_criterion');
var previousSize = $('.openassessment_criterion').length;
container.remove(1);
var newSize = $('.openassessment_criterion').length;
expect(newSize).toEqual(previousSize - 1);
});
it("requests an invalid template", function () {
var error = false;
try{
container.getHtmlTemplate('not_a_template');
} catch (e) {
error = true;
}
expect(error).toBe(true);
});
it("installs delete buttons", function () {
container.installDeleteButtons($('#openassessment_criterion_list'));
});
it("parses out unacceptable container items", function () {
container.element.append("<p> Hello, not an element here. </p>");
var error = false;
try{
container.getItemValues();
} catch (e) {
error = true;
}
expect(error).toBe(true);
});
it("returns correct item dictionary", function () {
var items = container.getItemValues();
var item = items[0];
expect(item.name).toEqual(jasmine.any(String));
expect(item.prompt).toEqual(jasmine.any(String));
expect(item.feedback).toEqual(jasmine.any(String));
expect(item.options).toEqual(jasmine.any(Array));
expect(item.options[0].name).toEqual(jasmine.any(String));
expect(parseInt(item.options[0].points)).toEqual(jasmine.any(Number));
expect(item.options[0].explanation).toEqual(jasmine.any(String));
});
it("checks for undefined selection type", function () {
var error = false;
try{
container.add("lolz this isn't a valid selection type");
} catch (e) {
error = true;
}
expect(error).toBe(true);
});
});
\ No newline at end of file
/**
* Created by gward on 7/9/14.
*/
/**
Container to store arbitrary DOM elements for insertion, deletion and installation of self-delete buttons
Args:
element (DOM element): The DOM element representing the container (usually an OL or a UL)
selectorDictionary (dict): Has keys which map element (str) to Container Item classes to which they are related
Returns:
OpenAssessment.Container
**/
OpenAssessment.Container = function(element, selectorDictionary){
this.element = element;
this.selectorDictionary = selectorDictionary;
this.installDeleteButtons(element);
};
OpenAssessment.Container.prototype = {
/**
Adds a new item of the specified type to the DOM.
If the type is unrecognized,
Throws:
An error that alerts the user that an unknown type was attempted to be added to the container.
**/
add: function(selectorString){
var type = this.selectorDictionary[selectorString];
if (type == undefined){
throw 'The string: ('+ selectorString + ') is not known by this container.';
}
this.element.append(this.getHtmlTemplate(selectorString));
},
/**
Removes a specified item from the DOM. This is distinct from (and not used by) delete buttons.
Args:
index(int): The index of the specified item to remove.
**/
remove: function(index){
var count = 0;
$(this.element).children().each(function(){
if (count == index){
$(this).remove();
return;
}
count++;
});
},
/**
Installs delete buttons on all sections of a certain element.
Called both when we create a container, and when we add new elements.
Args:
liveElement (Jquery Element): an element that allows us to define the scope of delete button creation.
**/
installDeleteButtons: function(liveElement){
$('.openassessment_delete_button', liveElement).each(function() {
$(this).click(function () {
liveElement.closest('.openassessment_deletable').remove();
});
});
},
/**
Gets the values that each container defines for itself, in the order in which they are
presented in the DOM.
Returns:
(list of ...): The list of the values that each container item associates with itself.
Throws:
An exception which notifies the user if there is an element in their container that is not
recognized as a part of the containers definition.
**/
getItemValues: function () {
var values = [];
var container = this;
$(this.element).children().each(function(){
//Attempts to find the type of a given element
var classes = $(this).attr('class').split(/\s+/);
var type = undefined;
for(var i = 0; i < classes.length; i++){
var c = classes[i];
if (container.selectorDictionary[c] != undefined){
type = container.selectorDictionary[c];
}
}
// If we couldn't resolve the type, we throw an exception.
if (type == undefined){
throw 'An item with classes (' + classes.join(' ') +
') was not found in a dict of known container items.';
}
var item = new type($(this));
var value = item.getFieldValues();
values.push(value);
});
return values;
},
/**
Returns the specific HTML template that is defined for its container items.
Returns:
(str): The HTML template associated with the container item's type.
**/
getHtmlTemplate: function(selectorString){
var selector = '.' + selectorString + '.openassessment_template';
var element = $(selector, this.element);
if (element.length == 0){
throw "There is not an element which matches the selector (" + selector + ")";
}
// Because we are assuming each template will be found within a div which it is the sole occupant of,
// we can make this call safely to get the body and enclosing tab.
return element.parent().html();
}
};
/**
The RubricOption Class used to construct and maintain references to rubric options from within an options
container object. Constructs a new RubricOption element.
Args:
parent (OpenAssessment.Container): The container that the option is a member of.
Returns:
OpenAssessment.RubricOption
**/
OpenAssessment.RubricOption = function(element){
this.element = element;
};
OpenAssessment.RubricOption.prototype = {
/**
Finds the values currently entered in the Option's fields, and returns them in a dictionary to the user.
Returns:
(dict) of the form:
{
'name': 'Real Bad',
'points': 1,
'explanation': 'Essay was primarily composed of emojis.'
}
**/
getFieldValues: function (){
var name = $('.openassessment_criterion_option_name', this.element).prop('value');
var points = $('.openassessment_criterion_option_points', this.element).prop('value');
var explanation = $('.openassessment_criterion_option_explanation', this.element).prop('value');
return {
'name': name,
'points': points,
'explanation': explanation
};
}
};
/**
The RubricCriterion Class is used to construct and get information from a rubric element within
the DOM.
Args:
element (JQuery Object): The selection which describes the scope of the criterion.
Returns:
OpenAssessment.RubricCriterion
**/
OpenAssessment.RubricCriterion = function(element){
this.element = element;
this.optionContainer = new OpenAssessment.Container(
$('.openassessment_criterion_option_list'),
{
'openassessment_criterion_option': OpenAssessment.RubricOption
}
);
};
OpenAssessment.RubricCriterion.prototype = {
/**
Finds the values currently entered in the Criterion's fields, and returns them in a dictionary to the user.
Returns:
(dict) of the form:
{
'name': 'Emoji Content',
'prompt': 'How expressive was the author with their words, and how much did they rely on emojis?',
'feedback': 'optional',
'options': [
{
'name': 'Real Bad',
'points': 1,
'explanation': 'Essay was primarily composed of emojis.'
}
...
]
}
**/
getFieldValues: function (){
var name = $('.openassessment_criterion_name', this.element).prop('value');
var prompt = $('.openassessment_criterion_prompt', this.element).prop('value');
var feedback = $('.openassessment_criterion_feedback', this.element).prop('value');
var options = this.optionContainer.getItemValues();
return {
'name': name,
'prompt': prompt,
'options': options,
'feedback': feedback
};
}
};
\ No newline at end of file
...@@ -44,7 +44,7 @@ OpenAssessment.StudioView = function(runtime, element, server) { ...@@ -44,7 +44,7 @@ OpenAssessment.StudioView = function(runtime, element, server) {
// Captures the HTML definition of the original criterion element. This will be the template // Captures the HTML definition of the original criterion element. This will be the template
// used for all other criterion creations // used for all other criterion creations
var criterionHtml = $("#openassessment_criterion_1", liveElement).parent().html(); var criterionHtml = $(".openassessment_criterion", liveElement).parent().html();
// Replaces all instances of the original ID (1) with the new fake ID in the string // Replaces all instances of the original ID (1) with the new fake ID in the string
// representation of the Criterion LI. This is our new template, with a C-C-C marker to replace. // representation of the Criterion LI. This is our new template, with a C-C-C marker to replace.
...@@ -52,7 +52,7 @@ OpenAssessment.StudioView = function(runtime, element, server) { ...@@ -52,7 +52,7 @@ OpenAssessment.StudioView = function(runtime, element, server) {
// Captures the HTML definition of the original option element. Note that there are TWO template // Captures the HTML definition of the original option element. Note that there are TWO template
// tags that need to be replaced "C-C-C" for the Criterion ID, and "O-O-O" for the option ID. // tags that need to be replaced "C-C-C" for the Criterion ID, and "O-O-O" for the option ID.
var optionHtml = $("#openassessment_criterion_1_option_1", liveElement).parent().html(); var optionHtml = $(".openassessment_criterion_option", liveElement).parent().html();
var criteriaReplaced = optionHtml.replace(new RegExp("criterion_1", "g"), "criterion_C-C-C"); var criteriaReplaced = optionHtml.replace(new RegExp("criterion_1", "g"), "criterion_C-C-C");
this.optionHtmlTemplate = criteriaReplaced.replace(new RegExp("option_1", "g"), "option_O-O-O"); this.optionHtmlTemplate = criteriaReplaced.replace(new RegExp("option_1", "g"), "option_O-O-O");
......
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