Commit 776be60f by Tim Krones

Merge pull request #33 from open-craft/custom-item-colors

Allow authors to set background and text color of draggable items
parents 8dbe34cf ce1314c1
......@@ -26,47 +26,67 @@ class DragAndDropBlock(XBlock):
display_name="Title",
help="The title of the Drag and Drop that is displayed to the user",
scope=Scope.settings,
default="Drag and Drop"
default="Drag and Drop",
)
show_title = Boolean(
display_name="Show title",
help="Display the title to the user?",
scope=Scope.settings,
default=True
default=True,
)
question_text = String(
display_name="Question text",
help="The question text that is displayed to the user",
scope=Scope.settings,
default=""
default="",
)
weight = Float(
display_name="Weight",
help="This is the maximum score that the user receives when he/she successfully completes the problem",
scope=Scope.settings,
default=1
default=1,
)
item_background_color = String(
display_name="Item background color",
help=(
"Background color to use for draggable items. "
"Defaults to color specified by current theme, or blue if no theme is set."
),
scope=Scope.settings,
default="",
)
item_text_color = String(
display_name="Item text color",
help=(
"Text color to use for draggable items. "
"Defaults to color specified by current theme, or white if no theme is set."
),
scope=Scope.settings,
default="",
)
data = Dict(
display_name="Drag and Drop",
help="JSON spec as generated by the builder",
scope=Scope.content,
default=DEFAULT_DATA
default=DEFAULT_DATA,
)
item_state = Dict(
help="How the student has interacted with the problem",
scope=Scope.user_state,
default={}
default={},
)
completed = Boolean(
help="The student has completed the problem at least once",
scope=Scope.user_state,
default=False
default=False,
)
has_score = True
......@@ -138,6 +158,8 @@ class DragAndDropBlock(XBlock):
self.show_title = submissions['show_title']
self.question_text = submissions['question_text']
self.weight = float(submissions['weight'])
self.item_background_color = submissions['item_background_color']
self.item_text_color = submissions['item_text_color']
self.data = submissions['data']
return {
......@@ -255,6 +277,11 @@ class DragAndDropBlock(XBlock):
data['show_title'] = self.show_title
data['question_text'] = self.question_text
if self.item_background_color:
data['item_background_color'] = self.item_background_color
if self.item_text_color:
data['item_text_color'] = self.item_text_color
return data
def _get_item_state(self):
......
......@@ -54,18 +54,24 @@
}
.xblock--drag-and-drop .drag-container .option {
width: 190px;
font-size: 14px;
position: relative;
display: inline-block;
width: auto;
border-radius: 3px;
margin: 1%;
padding: 10px;
background: #2e83cd;
font-size: 14px;
color: #fff;
position: relative;
float: left;
display: inline;
opacity: 1;
/* Some versions of the drag and drop library try to fiddle with this */
z-index: 10 !important;
margin-bottom: 5px;
padding: 10px;
opacity: 1;
}
.xblock--drag-and-drop .drag-container .ui-draggable-dragging {
box-shadow: 0 16px 32px 0 rgba(0,0,0,.3);
border: 1px solid #ccc;
opacity: .65;
}
.xblock--drag-and-drop .drag-container .option img {
......
......@@ -183,6 +183,12 @@
margin-bottom: 30px;
}
.xblock--drag-and-drop .item-styles-form .item-styles-form-help {
margin-top: 5px;
font-size: small;
}
.xblock--drag-and-drop .item-styles-form,
.xblock--drag-and-drop .items-form {
margin-bottom: 30px;
}
......
......@@ -222,13 +222,13 @@ function DragAndDropBlock(runtime, element) {
is_visible: item_state && !item_state.submitting_location,
has_value: Boolean(item_state && 'input' in item_state),
value : (item_state && item_state.input) || '',
class_name: undefined,
class_name: undefined
};
if (input.has_value && !item_state.submitting_input) {
input.class_name = item_state.correct_input ? 'correct' : 'incorrect';
}
}
return {
var itemProperties = {
value: item.id,
drag_disabled: Boolean(item_state || state.state.finished),
width: item.size.width,
......@@ -240,6 +240,13 @@ function DragAndDropBlock(runtime, element) {
input: input,
content_html: item.backgroundImage ? '<img src="' + item.backgroundImage + '"/>' : item.displayName
};
if (state.item_background_color) {
itemProperties.background_color = state.item_background_color;
}
if (state.item_text_color) {
itemProperties.color = state.item_text_color;
}
return itemProperties;
});
var context = {
......
......@@ -413,6 +413,8 @@ function DragAndDropEditBlock(runtime, element) {
'show_title': $(element).find('.show-title').is(':checked'),
'weight': $(element).find('.weight').val(),
'question_text': $(element).find('.question-text').val(),
'item_background_color': $(element).find('.item-background-color').val(),
'item_text_color': $(element).find('.item-text-color').val(),
'data': _fn.data,
};
......
......@@ -40,11 +40,23 @@
};
var itemTemplate = function(item) {
var style = {
width: item.width,
height: item.height,
top: item.top,
left: item.left,
position: item.position
};
if (item.background_color) {
style['background-color'] = item.background_color;
}
if (item.color) {
style.color = item.color;
}
return (
h('div.option', {className: item.class_name,
attributes: {'data-value': item.value, 'data-drag-disabled': item.drag_disabled},
style: {width: item.width, height: item.height,
top: item.top, left: item.left, position: item.position}}, [
style: style}, [
h('div', {innerHTML: item.content_html}),
itemInputTemplate(item.input)
])
......
......@@ -64,6 +64,32 @@
<h3>Items</h3>
</header>
<section class="tab-content">
<form class="item-styles-form">
<h3 id="item-background-color-label">
{% trans "Background color" %}
</h3>
<input class="item-background-color"
placeholder="e.g. blue or #0000ff"
value="{{ self.item_background_color}}"
aria-labelledby="item-background-color-label"
aria-describedby="item-background-color-description">
<div id="item-background-color-description" class="item-styles-form-help">
{{ self.fields.item_background_color.help }}
</div>
<h3 id="item-text-color-label">
{% trans "Text color" %}
</h3>
<input class="item-text-color"
placeholder="e.g. white or #ffffff"
value="{{ self.item_text_color}}"
aria-labelledby="item-text-color-label"
aria-describedby="item-text-color-description">
<div id="item-text-color-description" class="item-styles-form-help">
{{ self.fields.item_text_color.help }}
</div>
</form>
</section>
<section class="tab-content">
<form class="items-form"></form>
</section>
<footer class="tab-footer">
......
# Installs xblock-sdk and dependencies needed to run the tests suite.
# Run this script inside a fresh virtual environment.
pip install -e git://github.com/edx/xblock-sdk.git#egg=xblock-sdk
cd $VIRTUAL_ENV/src/xblock-sdk/ && pip install -r requirements.txt \
&& pip install -r test-requirements.txt && cd -
cd $VIRTUAL_ENV/src/xblock-sdk/ && pip install -r requirements/base.txt \
&& pip install -r requirements/test.txt && cd -
python setup.py develop
......@@ -21,6 +21,12 @@ if __name__ == "__main__":
# Configure a range of ports in case the default port of 8081 is in use
os.environ.setdefault("DJANGO_LIVE_TEST_SERVER_ADDRESS", "localhost:8081-8099")
try:
os.mkdir('var')
except OSError:
# May already exist.
pass
from django.core.management import execute_from_command_line
args = sys.argv[1:]
paths = [arg for arg in args if arg[0] != '-']
......
......@@ -50,6 +50,9 @@ class BaseIntegrationTest(SeleniumBaseTest):
def _get_feedback_message(self):
return self._page.find_element_by_css_selector(".feedback .message")
def scroll_down(self, pixels=50):
self.browser.execute_script("$(window).scrollTop({})".format(pixels))
def get_element_html(self, element):
return element.get_attribute('innerHTML').strip()
......
......@@ -129,9 +129,11 @@ class InteractionTestFixture(BaseIntegrationTest):
input_div = self._get_input_div_by_value(item_key)
self.wait_until_has_class('correct', input_div)
self.wait_until_exists('.reset-button')
self.wait_until_html_in(self.feedback['final'], self._get_feedback_message())
# Scroll "Reset exercise" button into view to make sure Selenium can successfully click it
self.scroll_down()
reset = self._page.find_element_by_css_selector('.reset-button')
reset.click()
......
from ddt import ddt, unpack, data
from tests.integration.test_base import BaseIntegrationTest
class Colors(object):
WHITE = 'rgb(255, 255, 255)'
BLUE = 'rgb(46, 131, 205)'
GREY = 'rgb(237, 237, 237)'
CORAL = '#ff7f50'
CORNFLOWERBLUE = 'cornflowerblue'
@classmethod
def rgb(cls, color):
if color in (cls.WHITE, cls.BLUE, cls.GREY):
return color
elif color == cls.CORAL:
return 'rgb(255, 127, 80)'
elif color == cls.CORNFLOWERBLUE:
return 'rgb(100, 149, 237)'
@ddt
class TestDragAndDropRender(BaseIntegrationTest):
"""
Verifying Drag and Drop XBlock rendering against default data - if default data changes this would probably broke
"""
PAGE_TITLE = 'Drag and Drop v2'
PAGE_ID = 'drag_and_drop_v2'
def setUp(self):
super(TestDragAndDropRender, self).setUp()
scenario_xml = "<vertical_demo><drag-and-drop-v2/></vertical_demo>"
ITEM_PROPERTIES = [
{'text': '1', 'style_settings': {'width': '190px', 'height': 'auto'}},
{'text': '2', 'style_settings': {'width': '190px', 'height': 'auto'}},
{'text': 'X', 'style_settings': {'width': '100px', 'height': '100px'}},
]
def load_scenario(self, item_background_color="", item_text_color=""):
scenario_xml = """
<vertical_demo>
<drag-and-drop-v2 item_background_color='{item_background_color}' item_text_color='{item_text_color}' />
</vertical_demo>
""".format(item_background_color=item_background_color, item_text_color=item_text_color)
self._add_scenario(self.PAGE_ID, self.PAGE_TITLE, scenario_xml)
self.browser.get(self.live_server_url)
self._page = self.go_to_page(self.PAGE_TITLE)
def _test_style(self, item, style_settings):
style = item.get_attribute('style')
def _get_style(self, selector, style):
return self.browser.execute_script(
'return getComputedStyle($("{selector}").get(0)).{style}'.format(selector=selector, style=style)
)
def _test_style(self, element, style_settings, element_type):
style = element.get_attribute('style')
for style_prop, expected_value in style_settings.items():
if style_prop == 'color' or style_prop == 'background-color' and expected_value.startswith('#'):
expected_value = Colors.rgb(expected_value)
expected = u"{0}: {1}".format(style_prop, expected_value)
self.assertIn(expected, style)
if element_type == "item":
self._test_item_style(element, style_settings, style)
def _test_item_style(self, item, style_settings, style):
# Check background color
background_color_property = 'background-color'
if background_color_property not in style_settings:
self.assertNotIn(background_color_property, style)
expected_background_color = Colors.BLUE
else:
expected_background_color = Colors.rgb(style_settings['background-color'])
background_color = self._get_style('.items .option', 'backgroundColor')
self.assertEquals(background_color, expected_background_color)
# Check text color
color_property = 'color'
if color_property not in style_settings:
self.assertNotIn(' ' + color_property, style) # Leading space makes sure that
# test does not find "color" in "background-color"
expected_color = Colors.WHITE
else:
expected_color = Colors.rgb(style_settings['color'])
color = self._get_style('.items .option', 'color')
self.assertEquals(color, expected_color)
def test_items(self):
self.load_scenario()
items = self._get_items()
self.assertEqual(len(items), 3)
self.assertEqual(items[0].get_attribute('data-value'), '0')
self.assertEqual(items[0].text, '1')
self.assertIn('ui-draggable', self.get_element_classes(items[0]))
self._test_style(items[0], {'width': '190px', 'height': 'auto'})
for index, item in enumerate(items):
self.assertEqual(item.get_attribute('data-value'), str(index))
self.assertEqual(item.text, self.ITEM_PROPERTIES[index]['text'])
self.assertIn('ui-draggable', self.get_element_classes(item))
self._test_style(item, self.ITEM_PROPERTIES[index]['style_settings'], element_type='item')
@unpack
@data(
(Colors.CORNFLOWERBLUE, Colors.GREY),
(Colors.CORAL, ''),
('', Colors.GREY),
)
def test_items_custom_colors(self, item_background_color, item_text_color):
self.load_scenario(item_background_color, item_text_color)
self.assertEqual(items[1].get_attribute('data-value'), '1')
self.assertEqual(items[1].text, '2')
self.assertIn('ui-draggable', self.get_element_classes(items[1]))
self._test_style(items[1], {'width': '190px', 'height': 'auto'})
items = self._get_items()
self.assertEqual(len(items), 3)
color_settings = {}
if item_background_color:
color_settings['background-color'] = item_background_color
if item_text_color:
color_settings['color'] = item_text_color
self.assertEqual(items[2].get_attribute('data-value'), '2')
self.assertEqual(items[2].text, 'X')
self.assertIn('ui-draggable', self.get_element_classes(items[2]))
self._test_style(items[2], {'width': '100px', 'height': '100px'})
for index, item in enumerate(items):
self.assertEqual(item.get_attribute('data-value'), str(index))
self.assertEqual(item.text, self.ITEM_PROPERTIES[index]['text'])
self.assertIn('ui-draggable', self.get_element_classes(item))
self._test_style(
item, dict(self.ITEM_PROPERTIES[index]['style_settings'], **color_settings), element_type='item'
)
def test_zones(self):
self.load_scenario()
zones = self._get_zones()
self.assertEqual(len(zones), 2)
self.assertEqual(zones[0].get_attribute('data-zone'), 'Zone 1')
self.assertIn('ui-droppable', self.get_element_classes(zones[0]))
self._test_style(zones[0], {'top': '200px', 'left': '120px', 'width': '200px', 'height': '100px'})
self._test_style(
zones[0], {'top': '200px', 'left': '120px', 'width': '200px', 'height': '100px'}, element_type='zone'
)
self.assertEqual(zones[1].get_attribute('data-zone'), 'Zone 2')
self.assertIn('ui-droppable', self.get_element_classes(zones[1]))
self._test_style(zones[1], {'top': '360px', 'left': '120px', 'width': '200px', 'height': '100px'})
self._test_style(
zones[1], {'top': '360px', 'left': '120px', 'width': '200px', 'height': '100px'}, element_type='zone'
)
def test_feedback(self):
self.load_scenario()
feedback_message = self._get_feedback_message()
self.assertEqual(feedback_message.text, "Intro Feed")
def test_background_image(self):
self.load_scenario()
bg_image = self.browser.execute_script('return jQuery(".target-img").css("background-image")')
image_path = '/resource/drag-and-drop-v2/public/img/triangle.png'
self.assertEqual(bg_image, 'url("{0}{1}")'.format(self.live_server_url, image_path))
......@@ -61,6 +61,8 @@ def test_studio_submit():
'display_name': "Test Drag & Drop",
'show_title': True,
'question_text': "Question Drag & Drop",
'item_background_color': 'cornflowerblue',
'item_text_color': 'coral',
'weight': '5',
'data': {
'foo': 1
......@@ -72,6 +74,8 @@ def test_studio_submit():
assert_equals(block.display_name, "Test Drag & Drop")
assert_equals(block.question_text, "Question Drag & Drop")
assert_equals(block.item_background_color, "cornflowerblue")
assert_equals(block.item_text_color, "coral")
assert_equals(block.weight, 5)
assert_equals(block.data, {'foo': 1})
......
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