Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
85 changes: 70 additions & 15 deletions cms/static/coffee/spec/views/metadata_edit_spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ define ["js/models/metadata", "js/collections/metadata", "js/views/metadata", "c
stringEntryTemplate = readFixtures('metadata-string-entry.underscore')
optionEntryTemplate = readFixtures('metadata-option-entry.underscore')
listEntryTemplate = readFixtures('metadata-list-entry.underscore')
checkboxEntryTemplate = readFixtures('metadata-checkbox-entry.underscore')

beforeEach ->
setFixtures($("<script>", {id: "metadata-editor-tpl", type: "text/template"}).text(editorTemplate))
appendSetFixtures($("<script>", {id: "metadata-number-entry", type: "text/template"}).text(numberEntryTemplate))
appendSetFixtures($("<script>", {id: "metadata-string-entry", type: "text/template"}).text(stringEntryTemplate))
appendSetFixtures($("<script>", {id: "metadata-option-entry", type: "text/template"}).text(optionEntryTemplate))
appendSetFixtures($("<script>", {id: "metadata-list-entry", type: "text/template"}).text(listEntryTemplate))
appendSetFixtures($("<script>", {id: "metadata-checkbox-entry", type: "text/template"}).text(checkboxEntryTemplate))

genericEntry = {
default_value: 'default value',
Expand Down Expand Up @@ -92,6 +94,21 @@ define ["js/models/metadata", "js/collections/metadata", "js/views/metadata", "c
value: "12:12:12"
}

checkboxEntry = {
default_value: ['bike'],
display_name: "Choose Answer",
explicitly_set: false,
field_name: "choose_answer",
help: "Do you have a bike, car, roller-skates?",
options: [
{"display_name": "I have a bike", "value": "bike"},
{"display_name": "I have a car", "value": "car"}
{"display_name": "I have a roller-skates", "value": "roller-skates"}
],
type: MetadataModel.CHECKBOX_TYPE,
value: ['car']
}


# Test for the editor that creates the individual views.
describe "MetadataView.Editor creates editors for each field", ->
Expand All @@ -116,29 +133,42 @@ define ["js/models/metadata", "js/collections/metadata", "js/views/metadata", "c
value: null
},
listEntry,
timeEntry
timeEntry,
{
default_value: null,
display_name: "Choose Answer",
explicitly_set: true,
field_name: "choose_answer",
help: "Do you have a bike?",
options: [
{"display_name": "I have a bike", "value": "bike"},
],
type: MetadataModel.CHECKBOX_TYPE,
value: null
}
]
)

it "creates child views on initialize, and sorts them alphabetically", ->
view = new MetadataView.Editor({collection: @model})
childModels = view.collection.models
expect(childModels.length).toBe(7)
expect(childModels.length).toBe(8)
# Be sure to check list view as well as other input types
childViews = view.$el.find('.setting-input, .list-settings')
expect(childViews.length).toBe(7)
expect(childViews.length).toBe(8)

verifyEntry = (index, display_name, type) ->
expect(childModels[index].get('display_name')).toBe(display_name)
verifyInputType(childViews[index], type)

verifyEntry(0, 'Display Name', 'text')
verifyEntry(1, 'Inputs', 'number')
verifyEntry(2, 'List', '')
verifyEntry(3, 'Show Answer', 'select-one')
verifyEntry(4, 'Time', 'text')
verifyEntry(5, 'Unknown', 'text')
verifyEntry(6, 'Weight', 'number')
verifyEntry(0, 'Choose Answer', 'checkbox')
verifyEntry(1, 'Display Name', 'text')
verifyEntry(2, 'Inputs', 'number')
verifyEntry(3, 'List', '')
verifyEntry(4, 'Show Answer', 'select-one')
verifyEntry(5, 'Time', 'text')
verifyEntry(6, 'Unknown', 'text')
verifyEntry(7, 'Weight', 'number')

it "returns its display name", ->
view = new MetadataView.Editor({collection: @model})
Expand Down Expand Up @@ -170,17 +200,17 @@ define ["js/models/metadata", "js/collections/metadata", "js/views/metadata", "c
it "returns modified values only", ->
view = new MetadataView.Editor({collection: @model})
childModels = view.collection.models
childModels[0].setValue('updated display name')
childModels[1].setValue(20)
childModels[1].setValue('updated display name')
childModels[2].setValue(20)
expect(view.getModifiedMetadataValues()).toEqual({
display_name : 'updated display name',
num_inputs: 20
})

# Tests for individual views.
assertInputType = (view, expectedType) ->
assertInputType = (view, expectedType, length=1) ->
input = view.$el.find('.setting-input')
expect(input.length).toEqual(1)
expect(input.length).toEqual(length)
verifyInputType(input[0], expectedType)

assertValueInView = (view, expectedValue) ->
Expand Down Expand Up @@ -210,7 +240,7 @@ define ["js/models/metadata", "js/collections/metadata", "js/views/metadata", "c
it "uses a text input type", ->
assertInputType(@view, 'text')

it "returns the intial value upon initialization", ->
it "returns the initial value upon initialization", ->
assertValueInView(@view, 'Word cloud')

it "can update its value in the view", ->
Expand Down Expand Up @@ -486,3 +516,28 @@ define ["js/models/metadata", "js/collections/metadata", "js/views/metadata", "c

it "has an update model method", ->
assertUpdateModel(@view, '12:12:12', '23:59:59')

describe "MetadataView.Checkbox is an option input type with clear functionality", ->
beforeEach ->
model = new MetadataModel(checkboxEntry)
@view = new MetadataView.Checkbox({model: model})

it "uses a checkbox input type", ->
assertInputType(@view, 'checkbox', 3)

it "returns the initial value upon initialization", ->
assertValueInView(@view, ['car'])

it "can update its value in the view", ->
assertCanUpdateView(@view, ['bike', 'roller-skates'])
assertCanUpdateView(@view, [])

it "has a clear method to revert to the model default", ->
assertClear(@view, ['bike'])

it "has an update model method", ->
assertUpdateModel(@view, null, ['roller-skates'])

it "does not update to a value that is not an option", ->
@view.setValueInEditor(['ship'])
expect(@view.getValueFromEditor()).toEqual(['car'])
1 change: 1 addition & 0 deletions cms/static/js/models/metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ define(["backbone"], function(Backbone) {
Metadata.LIST_TYPE = "List";
Metadata.VIDEO_LIST_TYPE = "VideoList";
Metadata.RELATIVE_TIME_TYPE = "RelativeTime";
Metadata.CHECKBOX_TYPE = "Checkbox";

return Metadata;
});
41 changes: 41 additions & 0 deletions cms/static/js/views/metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ function(BaseView, _, MetadataModel, AbstractEditor, VideoList) {
else if(model.getType() === MetadataModel.RELATIVE_TIME_TYPE) {
new Metadata.RelativeTime(data);
}
else if(model.getType() === MetadataModel.CHECKBOX_TYPE) {
new Metadata.Checkbox(data);
}
else {
// Everything else is treated as GENERIC_TYPE, which uses String editor.
new Metadata.String(data);
Expand Down Expand Up @@ -374,5 +377,43 @@ function(BaseView, _, MetadataModel, AbstractEditor, VideoList) {
}
});

Metadata.Checkbox = AbstractEditor.extend({

events : {
"change .input" : "updateModel",
"click .setting-clear" : "clear"
},

templateName: "metadata-checkbox-entry",

getValueFromEditor: function () {
return _.map(
this.$el.find('#' + this.uniqueId + " .input:checked"),
function (element) {
return element.value;
}
);
},

setValueInEditor: function (value) {
if (!$.isArray(value)) {
return false;
}

var checkboxes = this.$el.find('#' + this.uniqueId + " .input"),
matchedCheckboxes = checkboxes.filter(function() {
return $.inArray(this.value, value) !== -1;
});

if (matchedCheckboxes.length || !value.length) {
// Reset all checkboxes
checkboxes.prop('checked', false);
// Mark only matched checkboxes
matchedCheckboxes.prop('checked', true);
}
}
});


return Metadata;
});
59 changes: 59 additions & 0 deletions cms/static/sass/views/_unit.scss
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,65 @@ body.course.unit,.view-unit {
}
}
}


.metadata-checkboxes {

// label
.setting-label {
vertical-align: top;
}

// inputs and labels
.wrapper-checkbox-settings {
@include size(45%,100%);
display: inline-block;
min-width: ($baseline*5);

// enumerated fields
.checkbox-settings {
margin: 0;

.checkbox-settings-item {
margin-bottom: ($baseline/2);

&.a-center {
text-align: center;

.input-wrapper {
width: auto;
}
}

}

// inputs
.input {
height: auto;
width: auto;
display: block;
}

.input-wrapper {
vertical-align: baseline;
display: inline-block;
width: 10%;
}

.input-label {
display: inline-block;
vertical-align: top;
font-weight: 400;
font-size: .8em;
width: 80%;
}
}
}

.setting-clear {
vertical-align: top;
}
}
}
}
}
Expand Down
40 changes: 40 additions & 0 deletions cms/templates/js/metadata-checkbox-entry.underscore
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<div class="wrapper-comp-setting metadata-checkboxes">
<label
class="label setting-label"
for="<%= uniqueId %>-0">
<%= model.get('display_name') %>
</label>
<div class="wrapper-checkbox-settings" id="<%= uniqueId %>">
<ol class="checkbox-settings">
<% isSingleOption = model.get('options').length === 1 %>
<% _.each(model.get('options'), function(option, index) { %>
<% hasDisplayName = option.display_name !== undefined %>
<% if (isSingleOption && !hasDisplayName) { %>
<li class="checkbox-settings-item a-center">
<% } else { %>
<li class="checkbox-settings-item">
<% } %>
<span class="input-wrapper">
<input
id="<%= uniqueId %>-<%= index %>"
name="<%= uniqueId %>"
class="input setting-input"
type="checkbox"
value="<%= option['value'] %>" />
</span>
<% if (hasDisplayName) { %>
<label
for="<%= uniqueId %>-<%= index %>"
class="input-label">
<%= option['display_name'] %>
</label>
<% } %>
</li>
<% }) %>
</ol>
</div>
<button class="action setting-clear inactive" type="button" name="setting-clear" value="<%= gettext("Clear") %>" data-tooltip="<%= gettext("Clear") %>">
<i class="icon-undo"></i><span class="sr">"<%= gettext("Clear Value") %>"</span>
</button>
</div>
<span class="tip setting-help"><%= model.get('help') %></span>
4 changes: 4 additions & 0 deletions cms/templates/widgets/metadata-edit.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
<%static:include path="js/metadata-list-entry.underscore" />
</script>

<script id="metadata-checkbox-entry" type="text/template">
<%static:include path="js/metadata-checkbox-entry.underscore" />
</script>

<% showHighLevelSource='source_code' in editable_metadata_fields and editable_metadata_fields['source_code']['explicitly_set'] and enable_latex_compiler %>
<% metadata_field_copy = copy.copy(editable_metadata_fields) %>
## Delete 'source_code' field (if it exists) so metadata editor view does not attempt to render it.
Expand Down
4 changes: 4 additions & 0 deletions cms/templates/widgets/tabs/metadata-edit-tab.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,9 @@
<%static:include path="js/metadata-list-entry.underscore" />
</script>

<script id="metadata-checkbox-entry" type="text/template">
<%static:include path="js/metadata-checkbox-entry.underscore" />
</script>

<div class="wrapper-comp-settings metadata_edit" id="settings-tab" data-metadata='${json.dumps(editable_metadata_fields) | h}'/>

7 changes: 6 additions & 1 deletion common/lib/xmodule/xmodule/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import logging
import re

from xblock.fields import Field
from xblock.fields import Field, List
import datetime
import dateutil.parser

Expand Down Expand Up @@ -218,3 +218,8 @@ def timedelta_to_string(self, value):
if len(stringified) == 7:
stringified = '0' + stringified
return stringified


class Checkbox(List):
pass

7 changes: 5 additions & 2 deletions common/lib/xmodule/xmodule/x_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

from xblock.core import XBlock
from xblock.fields import Scope, Integer, Float, List, XBlockMixin, String
from xmodule.fields import RelativeTime
from xmodule.fields import RelativeTime, Checkbox
from xblock.fragment import Fragment
from xblock.runtime import Runtime
from xmodule.errortracker import exc_info_to_str
Expand Down Expand Up @@ -737,8 +737,11 @@ def jsonify_value(field, json_choice):
editor_type = "Generic"
values = field.values
if isinstance(values, (tuple, list)) and len(values) > 0:
editor_type = "Select"
values = [jsonify_value(field, json_choice) for json_choice in values]
if isinstance(field, Checkbox):
editor_type = "Checkbox"
else:
editor_type = "Select"
elif isinstance(field, Integer):
editor_type = "Integer"
elif isinstance(field, Float):
Expand Down