Add a list view for metadata editor.
This is to be used with VideoAlpha's functionality allowing a choice of multiple HTML5 video sources.
This commit is contained in:
1
cms/static/coffee/fixtures/metadata-list-entry.underscore
Symbolic link
1
cms/static/coffee/fixtures/metadata-list-entry.underscore
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../templates/js/metadata-list-entry.underscore
|
||||
@@ -3,12 +3,14 @@ describe "Test Metadata Editor", ->
|
||||
numberEntryTemplate = readFixtures('metadata-number-entry.underscore')
|
||||
stringEntryTemplate = readFixtures('metadata-string-entry.underscore')
|
||||
optionEntryTemplate = readFixtures('metadata-option-entry.underscore')
|
||||
listEntryTemplate = readFixtures('metadata-list-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))
|
||||
|
||||
genericEntry = {
|
||||
default_value: 'default value',
|
||||
@@ -62,6 +64,18 @@ describe "Test Metadata Editor", ->
|
||||
value: 10.2
|
||||
}
|
||||
|
||||
listEntry = {
|
||||
default_value: ["a thing", "another thing"],
|
||||
display_name: "List",
|
||||
explicitly_set: false,
|
||||
field_name: "list",
|
||||
help: "A list of things.",
|
||||
inheritable: false,
|
||||
options: [],
|
||||
type: CMS.Models.Metadata.LIST_TYPE,
|
||||
value: ["the first display value", "the second"]
|
||||
}
|
||||
|
||||
# Test for the editor that creates the individual views.
|
||||
describe "CMS.Views.Metadata.Editor creates editors for each field", ->
|
||||
beforeEach ->
|
||||
@@ -84,16 +98,17 @@ describe "Test Metadata Editor", ->
|
||||
{"display_name": "Never", "value": "never"}],
|
||||
type: "unknown type",
|
||||
value: null
|
||||
}
|
||||
},
|
||||
listEntry
|
||||
]
|
||||
)
|
||||
|
||||
it "creates child views on initialize, and sorts them alphabetically", ->
|
||||
view = new CMS.Views.Metadata.Editor({collection: @model})
|
||||
childModels = view.collection.models
|
||||
expect(childModels.length).toBe(5)
|
||||
expect(childModels.length).toBe(6)
|
||||
childViews = view.$el.find('.setting-input')
|
||||
expect(childViews.length).toBe(5)
|
||||
expect(childViews.length).toBe(6)
|
||||
|
||||
verifyEntry = (index, display_name, type) ->
|
||||
expect(childModels[index].get('display_name')).toBe(display_name)
|
||||
@@ -101,9 +116,10 @@ describe "Test Metadata Editor", ->
|
||||
|
||||
verifyEntry(0, 'Display Name', 'text')
|
||||
verifyEntry(1, 'Inputs', 'number')
|
||||
verifyEntry(2, 'Show Answer', 'select-one')
|
||||
verifyEntry(3, 'Unknown', 'text')
|
||||
verifyEntry(4, 'Weight', 'number')
|
||||
verifyEntry(2, 'List', 'text')
|
||||
verifyEntry(3, 'Show Answer', 'select-one')
|
||||
verifyEntry(4, 'Unknown', 'text')
|
||||
verifyEntry(5, 'Weight', 'number')
|
||||
|
||||
it "returns its display name", ->
|
||||
view = new CMS.Views.Metadata.Editor({collection: @model})
|
||||
@@ -146,27 +162,27 @@ describe "Test Metadata Editor", ->
|
||||
# Tests for individual views.
|
||||
assertInputType = (view, expectedType) ->
|
||||
input = view.$el.find('.setting-input')
|
||||
expect(input.length).toBe(1)
|
||||
expect(input[0].type).toBe(expectedType)
|
||||
expect(input.length).toEqual(1)
|
||||
expect(input[0].type).toEqual(expectedType)
|
||||
|
||||
assertValueInView = (view, expectedValue) ->
|
||||
expect(view.getValueFromEditor()).toBe(expectedValue)
|
||||
expect(view.getValueFromEditor()).toEqual(expectedValue)
|
||||
|
||||
assertCanUpdateView = (view, newValue) ->
|
||||
view.setValueInEditor(newValue)
|
||||
expect(view.getValueFromEditor()).toBe(newValue)
|
||||
expect(view.getValueFromEditor()).toEqual(newValue)
|
||||
|
||||
assertClear = (view, modelValue, editorValue=modelValue) ->
|
||||
view.clear()
|
||||
expect(view.model.getValue()).toBe(null)
|
||||
expect(view.model.getDisplayValue()).toBe(modelValue)
|
||||
expect(view.getValueFromEditor()).toBe(editorValue)
|
||||
expect(view.model.getDisplayValue()).toEqual(modelValue)
|
||||
expect(view.getValueFromEditor()).toEqual(editorValue)
|
||||
|
||||
assertUpdateModel = (view, originalValue, newValue) ->
|
||||
view.setValueInEditor(newValue)
|
||||
expect(view.model.getValue()).toBe(originalValue)
|
||||
expect(view.model.getValue()).toEqual(originalValue)
|
||||
view.updateModel()
|
||||
expect(view.model.getValue()).toBe(newValue)
|
||||
expect(view.model.getValue()).toEqual(newValue)
|
||||
|
||||
describe "CMS.Views.Metadata.String is a basic string input with clear functionality", ->
|
||||
beforeEach ->
|
||||
@@ -298,3 +314,23 @@ describe "Test Metadata Editor", ->
|
||||
|
||||
verifyDisallowedChars(@integerView)
|
||||
verifyDisallowedChars(@floatView)
|
||||
|
||||
describe "CMS.Views.Metadata.List allows the user to enter an ordered list of strings", ->
|
||||
beforeEach ->
|
||||
listModel = new CMS.Models.Metadata(listEntry)
|
||||
@listView = new CMS.Views.Metadata.List({model: listModel})
|
||||
|
||||
it "uses a text input type", ->
|
||||
assertInputType(@listView, 'text')
|
||||
|
||||
it "returns the initial value upon initialization", ->
|
||||
assertValueInView(@listView, ['the first display value', 'the second'])
|
||||
|
||||
it "updates its value correctly", ->
|
||||
assertCanUpdateView(@listView, ['a new item', 'another new item', 'a third'])
|
||||
|
||||
it "has a clear method to revert to the model default", ->
|
||||
assertClear(@listView, ['a thing', 'another thing'])
|
||||
|
||||
it "has an update model method", ->
|
||||
assertUpdateModel(@listView, null, ['a new value'])
|
||||
|
||||
@@ -111,3 +111,4 @@ CMS.Models.Metadata.SELECT_TYPE = "Select";
|
||||
CMS.Models.Metadata.INTEGER_TYPE = "Integer";
|
||||
CMS.Models.Metadata.FLOAT_TYPE = "Float";
|
||||
CMS.Models.Metadata.GENERIC_TYPE = "Generic";
|
||||
CMS.Models.Metadata.LIST_TYPE = "List";
|
||||
|
||||
@@ -27,6 +27,9 @@ CMS.Views.Metadata.Editor = Backbone.View.extend({
|
||||
model.getType() === CMS.Models.Metadata.FLOAT_TYPE) {
|
||||
new CMS.Views.Metadata.Number(data);
|
||||
}
|
||||
else if(model.getType() === CMS.Models.Metadata.LIST_TYPE) {
|
||||
new CMS.Views.Metadata.List(data);
|
||||
}
|
||||
else {
|
||||
// Everything else is treated as GENERIC_TYPE, which uses String editor.
|
||||
new CMS.Views.Metadata.String(data);
|
||||
@@ -310,3 +313,23 @@ CMS.Views.Metadata.Option = CMS.Views.Metadata.AbstractEditor.extend({
|
||||
}).prop('selected', true);
|
||||
}
|
||||
});
|
||||
|
||||
CMS.Views.Metadata.List = CMS.Views.Metadata.AbstractEditor.extend({
|
||||
|
||||
events : {
|
||||
"click .setting-clear" : "clear",
|
||||
"keypress .setting-input" : "showClearButton",
|
||||
"change input" : "updateModel"
|
||||
},
|
||||
|
||||
templateName: "metadata-list-entry",
|
||||
|
||||
getValueFromEditor: function () {
|
||||
return _.map(this.$el.find('#' + this.uniqueId).val().split(/,/),
|
||||
function (url) { return url.trim(); });
|
||||
},
|
||||
|
||||
setValueInEditor: function (value) {
|
||||
this.$el.find('.input').val(value.join(', '));
|
||||
}
|
||||
});
|
||||
|
||||
8
cms/templates/js/metadata-list-entry.underscore
Normal file
8
cms/templates/js/metadata-list-entry.underscore
Normal file
@@ -0,0 +1,8 @@
|
||||
<div class="wrapper-comp-setting">
|
||||
<label class="label setting-label" for="<%= uniqueId %>"><%= model.get('display_name')%></label>
|
||||
<input class="input setting-input" type="text" id="<%= uniqueId %>" value='<%= model.get("value") %>'/>
|
||||
<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>
|
||||
@@ -25,6 +25,10 @@
|
||||
<%static:include path="js/metadata-option-entry.underscore" />
|
||||
</script>
|
||||
|
||||
<script id="metadata-list-entry" type="text/template">
|
||||
<%static:include path="js/metadata-list-entry.underscore" />
|
||||
</script>
|
||||
|
||||
<% showHighLevelSource='source_code' in editable_metadata_fields and editable_metadata_fields['source_code']['explicitly_set'] %>
|
||||
<% 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.
|
||||
@@ -40,4 +44,4 @@
|
||||
<%include file="source-edit.html" />
|
||||
% endif
|
||||
|
||||
<div class="wrapper-comp-settings metadata_edit" id="settings-tab" data-metadata='${json.dumps(metadata_field_copy) | h}'/>
|
||||
<div class="wrapper-comp-settings metadata_edit" id="settings-tab" data-metadata='${json.dumps(metadata_field_copy) | h}'/>
|
||||
|
||||
@@ -10,7 +10,7 @@ from pkg_resources import resource_listdir, resource_string, resource_isdir
|
||||
from xmodule.modulestore import inheritance, Location
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError, InsufficientSpecificationError, InvalidLocationError
|
||||
|
||||
from xblock.core import XBlock, Scope, String, Integer, Float, ModelType
|
||||
from xblock.core import XBlock, Scope, String, Integer, Float, List, ModelType
|
||||
from xblock.fragment import Fragment
|
||||
from xblock.runtime import Runtime
|
||||
from xmodule.modulestore.locator import BlockUsageLocator
|
||||
@@ -766,7 +766,7 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
|
||||
# 2. Number editors for integers and floats.
|
||||
# 3. A generic string editor for anything else (editing JSON representation of the value).
|
||||
editor_type = "Generic"
|
||||
values = [] if field.values is None else copy.deepcopy(field.values)
|
||||
values = copy.deepcopy(field.values)
|
||||
if isinstance(values, tuple):
|
||||
values = list(values)
|
||||
if isinstance(values, list):
|
||||
@@ -783,11 +783,13 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
|
||||
editor_type = "Integer"
|
||||
elif isinstance(field, Float):
|
||||
editor_type = "Float"
|
||||
elif isinstance(field, List):
|
||||
editor_type = "List"
|
||||
metadata_fields[field.name] = {'field_name': field.name,
|
||||
'type': editor_type,
|
||||
'display_name': field.display_name,
|
||||
'value': field.to_json(value),
|
||||
'options': values,
|
||||
'options': [] if values is None else values,
|
||||
'default_value': field.to_json(default_value),
|
||||
'inheritable': inheritable,
|
||||
'explicitly_set': explicitly_set,
|
||||
|
||||
Reference in New Issue
Block a user