diff --git a/tests/integration/test_vectordraw.py b/tests/integration/test_vectordraw.py index 47d761b..882f5c2 100644 --- a/tests/integration/test_vectordraw.py +++ b/tests/integration/test_vectordraw.py @@ -29,6 +29,10 @@ def assert_not_present(self, parent, selector, errmsg): else: self.fail(errmsg) + def check_hidden_text(self, selector, expected_text): + hidden_text = self.browser.execute_script("return $('{}').text();".format(selector)) + self.assertEquals(hidden_text, expected_text) + def check_title_and_description(self, expected_title="Vector Drawing", expected_description=None): title = self.exercise.find_element_by_css_selector("h2") self.assertEquals(title.text, expected_title) @@ -70,6 +74,8 @@ def check_background(self, board, is_present=False): self.assertTrue(background.is_displayed()) src = background.get_attribute("xlink:href") self.assertEquals(src, "https://github.com/open-craft/jsinput-vectordraw/raw/master/Notes_and_Examples/2_boxIncline_multiVector/box_on_incline.png") + alt = background.get_attribute("alt") + self.assertEquals(alt, "A very informative description") else: self.assert_not_present( board, @@ -78,29 +84,42 @@ def check_background(self, board, is_present=False): ) def check_buttons(self, controls, add_vector_label="Add Selected Force"): + # "Add vector" button add_vector = controls.find_element_by_css_selector(".add-vector") self.assertEquals(add_vector.text, add_vector_label) + # "Reset" button reset = controls.find_element_by_css_selector(".reset") self.assertEquals(reset.text, "Reset") - undo = controls.find_element_by_css_selector(".undo") - undo.find_element_by_css_selector(".fa.fa-undo") + reset.find_element_by_css_selector(".sr") + self.check_hidden_text(".reset > .sr", "Reset board to initial state") + # "Redo" button redo = controls.find_element_by_css_selector(".redo") redo.find_element_by_css_selector(".fa.fa-repeat") + redo.find_element_by_css_selector(".sr") + self.check_hidden_text(".redo > .sr", "Redo last action") + # "Undo" button + undo = controls.find_element_by_css_selector(".undo") + undo.find_element_by_css_selector(".fa.fa-undo") + undo.find_element_by_css_selector(".sr") + self.check_hidden_text(".undo > .sr", "Undo last action") def check_vector_properties( self, menu, is_present=False, expected_label="Vector Properties", - expected_name=None, expected_length=None, expected_angle=None + expected_name=None, expected_tail=None, expected_length=None, expected_angle=None ): if is_present: vector_properties = menu.find_element_by_css_selector(".vector-properties") vector_properties_label = vector_properties.find_element_by_css_selector("h3") self.assertEquals(vector_properties_label.text, expected_label) - vector_name = vector_properties.find_element_by_css_selector(".vector-prop-name") - self.assertEquals(vector_name.text, "name: {}".format(expected_name or "-")) - vector_length = vector_properties.find_element_by_css_selector(".vector-prop-length") - self.assertEquals(vector_length.text, "length: {}".format(expected_length or "-")) - vector_angle = vector_properties.find_element_by_css_selector(".vector-prop-angle") - self.assertTrue(vector_angle.text.startswith("angle: {}".format(expected_angle or "-"))) + # Name + self.check_vector_property(vector_properties, "name", "select", "name:", expected_name or "-") + # Tail + self.check_vector_property(vector_properties, "tail", "input", "tail position:", expected_tail or "-") + # Length + self.check_vector_property(vector_properties, "length", "input", "length:", expected_length or "-") + # Angle + self.check_vector_property(vector_properties, "angle", "input", "angle:", expected_angle or "-") + # Slope vector_slope = vector_properties.find_element_by_css_selector(".vector-prop-slope") self.assertFalse(vector_slope.is_displayed()) else: @@ -110,13 +129,36 @@ def check_vector_properties( "If show_vector_properties is set to False, menu should not show vector properties." ) + def check_vector_property( + self, vector_properties, property_name, input_type, expected_label, expected_value=None + ): + vector_property = vector_properties.find_element_by_css_selector( + ".vector-prop-{}".format(property_name) + ) + vector_property_label = vector_property.find_element_by_css_selector( + "#vector-prop-{}-label".format(property_name) + ) + self.assertEquals(vector_property_label.text, expected_label) + vector_property_input = vector_property.find_element_by_css_selector(input_type) + self.assertEquals( + vector_property_input.get_attribute("aria-labelledby"), "vector-prop-{}-label".format(property_name) + ) + if input_type == "input": + self.assertEquals(vector_property_input.get_attribute("value"), expected_value) + else: + selected_option = vector_property_input.find_element_by_css_selector('option[selected="selected"]') + self.assertEquals(selected_option.text, expected_value) + def check_actions(self): actions = self.exercise.find_element_by_css_selector(".action") self.assertTrue(actions.is_displayed()) check = actions.find_element_by_css_selector(".check") self.assertEquals(check.text, "CHECK") + check.find_element_by_css_selector(".sr") + self.check_hidden_text(".check > .sr", "Check your answer") - def check_dropdown(self, controls, vectors=[], points=[]): + def check_add_dropdown(self, controls, vectors=[], points=[]): + # Check dropdown dropdown = controls.find_element_by_css_selector("select") if not vectors and not points: self.assert_not_present( @@ -125,11 +167,17 @@ def check_dropdown(self, controls, vectors=[], points=[]): "Dropdown should not list any vectors or points by default." ) else: - self.check_options(dropdown, vectors, "vector") + self.check_add_options(dropdown, vectors, "vector") non_fixed_points = [point for point in points if not point["fixed"]] - self.check_options(dropdown, non_fixed_points, "point") - - def check_options(self, dropdown, elements, element_type): + self.check_add_options(dropdown, non_fixed_points, "point") + # Check label + label_id = "element-list-add-label" + label_selector = "#" + label_id + controls.find_element_by_css_selector(label_selector) + self.check_hidden_text(label_selector, "Select element to add to board") + self.assertEquals(dropdown.get_attribute("aria-labelledby"), label_id) + + def check_add_options(self, dropdown, elements, element_type): element_options = dropdown.find_elements_by_css_selector('option[value^="{}-"]'.format(element_type)) self.assertEquals(len(element_options), len(elements)) for element, element_option in zip(elements, element_options): @@ -137,6 +185,30 @@ def check_options(self, dropdown, elements, element_type): option_disabled = element_option.get_attribute("disabled") self.assertEquals(bool(option_disabled), element["render"]) + def check_edit_dropdown(self, menu, vectors=[], points=[]): + vector_properties = menu.find_element_by_css_selector(".vector-properties") + # Check dropdown + dropdown = vector_properties.find_element_by_css_selector("select") + if not vectors and not points: + options = dropdown.find_elements_by_css_selector("option") + self.assertEquals(len(options), 1) + default_option = options[0] + self.assertEquals(default_option.get_attribute("value"), "-") + else: + if vectors: + self.check_edit_options(dropdown, vectors, "vector") + if points: + non_fixed_points = [point for point in points if not point["fixed"]] + self.check_edit_options(dropdown, non_fixed_points, "point") + + def check_edit_options(self, dropdown, elements, element_type): + element_options = dropdown.find_elements_by_css_selector('option[value^="{}-"]'.format(element_type)) + self.assertEquals(len(element_options), len(elements)) + for element, element_option in zip(elements, element_options): + self.assertEquals(element_option.text, element["name"]) + option_disabled = element_option.get_attribute("disabled") + self.assertNotEquals(bool(option_disabled), element["render"]) + def check_vectors(self, board, vectors): line_elements = board.find_elements_by_css_selector("line") point_elements = board.find_elements_by_css_selector("ellipse") @@ -168,7 +240,8 @@ def check_points(self, board, points): self.assertEquals(board_has_point, point["render"]) def board_has_line(self, position, line_elements): - return bool(self.find_line(position, line_elements)) + line = self.find_line(position, line_elements) + return bool(line) and self.line_has_title(line) and self.line_has_desc(line) def board_has_point(self, position, point_elements): return bool(self.find_point(position, point_elements)) @@ -181,6 +254,16 @@ def board_has_label(self, board, label_text): return True return False + def line_has_title(self, line): + title = line.find_element_by_css_selector("title") + title_id = title.get_attribute("id") + aria_labelledby = line.get_attribute("aria-labelledby") + return title_id == aria_labelledby + + def line_has_desc(self, line): + aria_describedby = line.get_attribute("aria-describedby") + return aria_describedby == "jxgboard1-vector-properties" + def find_line(self, position, line_elements): expected_line_position = position.items() for line in line_elements: @@ -197,8 +280,8 @@ def find_point(self, position, point_elements): expected_position = position.items() for point in point_elements: point_position = { - "cx": int(float(point.get_attribute("cx"))), - "cy": int(float(point.get_attribute("cy"))), + "cx": int(round(float(point.get_attribute("cx")))), + "cy": int(round(float(point.get_attribute("cy")))), }.items() if point_position == expected_position: return point @@ -214,8 +297,9 @@ def add_vector(self, board, vectors): # "Vector Properties" should display correct info self.check_vector_properties( menu, is_present=True, expected_label="Custom properties label", - expected_name="N", expected_length="4.00", expected_angle="45.00" + expected_name="N", expected_tail="2.00, 2.00", expected_length="4.00", expected_angle="45.00" ) + self.check_edit_dropdown(menu, vectors) def add_point(self, board, points): menu = self.exercise.find_element_by_css_selector(".menu") @@ -246,7 +330,7 @@ def redo(self, board, vectors): # "Vector Properties" should display correct info self.check_vector_properties( menu, is_present=True, expected_label="Custom properties label", - expected_name="N", expected_length="4.00", expected_angle="45.00" + expected_name="N", expected_tail="2.00, 2.00", expected_length="4.00", expected_angle="45.00" ) def reset(self, board, vectors, points): @@ -277,6 +361,21 @@ def check_status(self, answer_correct=True, expected_message="Test passed"): status_message = status.find_element_by_css_selector(".status-message") self.assertEquals(status_message.text, expected_message) + def change_property(self, property_name, new_value): + menu = self.exercise.find_element_by_css_selector(".menu") + vector_properties = menu.find_element_by_css_selector(".vector-properties") + vector_property = vector_properties.find_element_by_css_selector( + ".vector-prop-{}".format(property_name) + ) + vector_property_input = vector_property.find_element_by_css_selector("input") + # Enter new value + vector_property_input.clear() + vector_property_input.send_keys(new_value) + # Find "Update" button + update_button = vector_properties.find_element_by_css_selector(".vector-prop-update") + # Click "Update" button + update_button.click() + def test_defaults(self): self.load_scenario("xml/defaults.xml") @@ -305,9 +404,10 @@ def test_defaults(self): # Check menu menu = self.exercise.find_element_by_css_selector(".menu") controls = menu.find_element_by_css_selector(".controls") - self.check_dropdown(controls) + self.check_add_dropdown(controls) self.check_buttons(controls) self.check_vector_properties(menu, is_present=True) + self.check_edit_dropdown(menu) # Check actions self.check_actions() @@ -398,11 +498,12 @@ def test_custom_exercise(self, params): # Check menu menu = self.exercise.find_element_by_css_selector(".menu") controls = menu.find_element_by_css_selector(".controls") - self.check_dropdown(controls, vectors, points) + self.check_add_dropdown(controls, vectors, points) self.check_buttons(controls, add_vector_label="Custom button label") show_vector_properties = params["show_vector_properties"] if show_vector_properties: self.check_vector_properties(menu, is_present=True, expected_label="Custom properties label") + self.check_edit_dropdown(menu, vectors, points) else: self.check_vector_properties(menu) @@ -451,7 +552,7 @@ def test_select_vector(self, click_target): menu = self.exercise.find_element_by_css_selector(".menu") self.check_vector_properties( menu, is_present=True, expected_label="Custom properties label", - expected_name="N", expected_length="4.00", expected_angle="45.00" + expected_name="N", expected_tail="2.00, 2.00", expected_length="4.00", expected_angle="45.00" ) @data( @@ -758,3 +859,147 @@ def test_state(self, params): self.check_status( answer_correct=False, expected_message="Vector N does not start at correct point." ) + + @data( + { + "show_vector_properties": True, + "vectors": json.dumps([ + { + "name": "N", + "description": "Normal force - N", + "tail": [2, 2], + "length": 4, + "angle": 45, + "render": False, + "expected_line_position": {"x1": 347, "y1": 181, "x2": 402, "y2": 125}, + "expected_tail_position": {"cx": 347, "cy": 181}, + "expected_tip_position": {"cx": 411, "cy": 117}, + } + ]), + "points": json.dumps([]), + "expected_result": json.dumps({}) + } + ) + def test_change_tail_property(self, params): + self.load_scenario("xml/custom.xml", params=params) + board = self.exercise.find_element_by_css_selector("#jxgboard1") + # Board should not show vector initially + vectors = json.loads(params["vectors"]) + self.check_vectors(board, vectors) + # Add vector + self.add_vector(board, vectors) + # Change tail + self.change_property("tail", "3, 3") + # Check new position: Tail updated, tip updated + vectors[0]["expected_line_position"] = {'x1': 370, 'y1': 159, 'x2': 425, 'y2': 102} + vectors[0]["expected_tail_position"] = {'cx': 370, 'cy': 159} + vectors[0]["expected_tip_position"] = {'cx': 434, 'cy': 94} + self.check_vectors(board, vectors) + + @data( + { + "show_vector_properties": True, + "vectors": json.dumps([ + { + "name": "N", + "description": "Normal force - N", + "tail": [2, 2], + "length": 4, + "angle": 45, + "render": False, + "expected_line_position": {"x1": 347, "y1": 181, "x2": 402, "y2": 125}, + "expected_tail_position": {"cx": 347, "cy": 181}, + "expected_tip_position": {"cx": 411, "cy": 117}, + } + ]), + "points": json.dumps([]), + "expected_result": json.dumps({}) + } + ) + def test_change_length_property(self, params): + self.load_scenario("xml/custom.xml", params=params) + board = self.exercise.find_element_by_css_selector("#jxgboard1") + # Board should not show vector initially + vectors = json.loads(params["vectors"]) + self.check_vectors(board, vectors) + # Add vector + self.add_vector(board, vectors) + # Change tail + self.change_property("length", "6") + # Check new position: Tail unchanged, tip updated + vectors[0]["expected_line_position"] = {'x1': 347, 'y1': 181, 'x2': 434, 'y2': 93} + vectors[0]["expected_tail_position"] = {'cx': 347, 'cy': 181} + vectors[0]["expected_tip_position"] = {'cx': 443, 'cy': 85} + self.check_vectors(board, vectors) + + @data( + { + "show_vector_properties": True, + "vectors": json.dumps([ + { + "name": "N", + "description": "Normal force - N", + "tail": [2, 2], + "length": 4, + "angle": 45, + "render": False, + "expected_line_position": {"x1": 347, "y1": 181, "x2": 402, "y2": 125}, + "expected_tail_position": {"cx": 347, "cy": 181}, + "expected_tip_position": {"cx": 411, "cy": 117}, + } + ]), + "points": json.dumps([]), + "expected_result": json.dumps({}) + } + ) + def test_change_angle_property(self, params): + self.load_scenario("xml/custom.xml", params=params) + board = self.exercise.find_element_by_css_selector("#jxgboard1") + # Board should not show vector initially + vectors = json.loads(params["vectors"]) + self.check_vectors(board, vectors) + # Add vector + self.add_vector(board, vectors) + # Change tail + self.change_property("angle", "170") + # Check new position: Tail unchanged, tip updated + vectors[0]["expected_line_position"] = {'x1': 347, 'y1': 181, 'x2': 269, 'y2': 167} + vectors[0]["expected_tail_position"] = {'cx': 347, 'cy': 181} + vectors[0]["expected_tip_position"] = {'cx': 258, 'cy': 165} + self.check_vectors(board, vectors) + + @data( + { + "show_vector_properties": True, + "vectors": json.dumps([ + { + "name": "N", + "description": "Normal force - N", + "tail": [2, 2], + "length": 4, + "angle": 45, + "render": False, + "expected_line_position": {"x1": 347, "y1": 181, "x2": 402, "y2": 125}, + "expected_tail_position": {"cx": 347, "cy": 181}, + "expected_tip_position": {"cx": 411, "cy": 117}, + } + ]), + "points": json.dumps([]), + "expected_result": json.dumps({}) + } + ) + def test_change_property_invalid_input(self, params): + self.load_scenario("xml/custom.xml", params=params) + board = self.exercise.find_element_by_css_selector("#jxgboard1") + # Board should not show vector initially + vectors = json.loads(params["vectors"]) + self.check_vectors(board, vectors) + # Add vector + self.add_vector(board, vectors) + # Change tail + self.change_property("tail", "invalid") + # Check new position: Tail unchanged, tip unchanged + vectors[0]["expected_line_position"] = {'x1': 347, 'y1': 181, 'x2': 402, 'y2': 125} + vectors[0]["expected_tail_position"] = {'cx': 347, 'cy': 181} + vectors[0]["expected_tip_position"] = {'cx': 411, 'cy': 117} + self.check_vectors(board, vectors) diff --git a/tests/integration/xml/custom.xml b/tests/integration/xml/custom.xml index 6194836..6a89d9c 100644 --- a/tests/integration/xml/custom.xml +++ b/tests/integration/xml/custom.xml @@ -12,6 +12,7 @@ vector_properties_label="Custom properties label" background_url="https://github.com/open-craft/jsinput-vectordraw/raw/master/Notes_and_Examples/2_boxIncline_multiVector/box_on_incline.png" background_width="20" + background_description="A very informative description" vectors="{{ vectors }}" points="{{ points }}" expected_result="{{ expected_result }}" diff --git a/vectordraw/public/css/vectordraw.css b/vectordraw/public/css/vectordraw.css index 485341f..90549b2 100644 --- a/vectordraw/public/css/vectordraw.css +++ b/vectordraw/public/css/vectordraw.css @@ -20,6 +20,8 @@ pointer-events: none; /* prevents cursor from turning into caret when over a label */ } +/* Menu */ + .vectordraw_block .menu { width: 100%; } @@ -41,7 +43,7 @@ font-size: 18px; } -.vectordraw_block .menu .controls button { +.vectordraw_block .menu button { border: 1px solid #1f628d; border-radius: 5px; margin: 4px 0; @@ -53,7 +55,7 @@ text-decoration: none; } -.vectordraw_block .menu .controls button:hover { +.vectordraw_block .menu button:hover { background: #c2e0f4; background-image: -webkit-linear-gradient(top, #c2e0f4, #add5f0); background-image: -moz-linear-gradient(top, #c2e0f4, #add5f0); @@ -85,12 +87,39 @@ margin: 0 0 5px; } -.vectordraw_block .menu .vector-properties .vector-prop-bold { - font-weight: bold; +.vectordraw_block .menu .vector-properties .vector-prop-list { + display: table; + width: 100% } -.vectordraw_block .menu .vector-prop-slope { - display: none; +.vectordraw_block .menu .vector-properties .vector-prop-list .row { + display: table-row; +} + +.vectordraw_block .menu .vector-properties .vector-prop-list .row .vector-prop-name, +.vectordraw_block .menu .vector-properties .vector-prop-list .row .vector-prop-tail, +.vectordraw_block .menu .vector-properties .vector-prop-list .row .vector-prop-tail-label, +.vectordraw_block .menu .vector-properties .vector-prop-list .row .vector-prop-length, +.vectordraw_block .menu .vector-properties .vector-prop-list .row .vector-prop-length-label, +.vectordraw_block .menu .vector-properties .vector-prop-list .row .vector-prop-angle, +.vectordraw_block .menu .vector-properties .vector-prop-list .row .vector-prop-angle-label, +.vectordraw_block .menu .vector-properties .vector-prop-list .row .vector-prop-slope, +.vectordraw_block .menu .vector-properties .vector-prop-list .row .vector-prop-slope-label, +.vectordraw_block .menu .vector-properties .vector-prop-list .row .vector-prop-update { + display: table-cell; + width: 50% +} + +.vectordraw_block .menu .vector-properties .vector-prop-list .row .vector-prop-name, +.vectordraw_block .menu .vector-properties .vector-prop-list .row .vector-prop-tail, +.vectordraw_block .menu .vector-properties .vector-prop-list .row .vector-prop-angle { + padding-right: 5px; +} + +.vectordraw_block .menu .vector-properties .vector-prop-list .row select, +.vectordraw_block .menu .vector-properties .vector-prop-list .row input { + float: right; + width: 50%; } .vectordraw_block .action button { @@ -101,7 +130,7 @@ } /* Make sure screen-reader content is hidden in the workbench: */ -.vectordraw_block .action .sr { +.vectordraw_block .sr { display: none; border: 0; clip: rect(0 0 0 0); diff --git a/vectordraw/public/css/vectordraw_edit.css b/vectordraw/public/css/vectordraw_edit.css index 49e7a61..2226656 100644 --- a/vectordraw/public/css/vectordraw_edit.css +++ b/vectordraw/public/css/vectordraw_edit.css @@ -27,3 +27,107 @@ pointer-events: none; /* prevents cursor from turning into caret when over a label */ } +/* Menu */ + +.vectordraw_edit_block .menu { + width: 100%; +} + +.vectordraw_edit_block .menu .controls { + border-top-left-radius: 5px; + border-top-right-radius: 5px; + border-top: 2px solid #1f628d; + border-left: 2px solid #1f628d; + border-right: 2px solid #1f628d; + padding: 3px; + background-color: #e0e0e0; + font-size: 0; +} + +.vectordraw_edit_block .menu .controls select { + width: 160px; + margin-right: 3px; + font-size: 18px; +} + +.vectordraw_edit_block .menu button { + border: 1px solid #1f628d; + border-radius: 5px; + margin: 4px 0; + padding: 5px 10px 5px 10px; + box-shadow: 0 1px 3px #666; + background-color: #c2e0f4; + color: #1f628d; + font-size: 14px; + text-decoration: none; +} + +.vectordraw_edit_block .menu button:hover { + background: #c2e0f4; + background-image: -webkit-linear-gradient(top, #c2e0f4, #add5f0); + background-image: -moz-linear-gradient(top, #c2e0f4, #add5f0); + background-image: -ms-linear-gradient(top, #c2e0f4, #add5f0); + background-image: -o-linear-gradient(top, #c2e0f4, #add5f0); + background-image: linear-gradient(to bottom, #c2e0f4, #add5f0); + text-decoration: none; +} + +.vectordraw_edit_block .menu .vector-properties { + border-top: 2px solid #1f628d; + border-left: 2px solid #1f628d; + border-right: 2px solid #1f628d; + border-bottom: 0px none; + padding: 10px; + font-size: 16px; + line-height: 1.25; + background-color: #f7f7f7; +} + +.vectordraw_edit_block .menu .vector-properties h3 { + font-size: 16px; + margin: 0 0 5px; +} + +.vectordraw_edit_block .menu .vector-properties .vector-prop-list { + display: table; + width: 100% +} + +.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row { + display: table-row; +} + +.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-prop-name, +.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-prop-tail, +.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-prop-tail-label, +.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-prop-length, +.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-prop-length-label, +.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-prop-angle, +.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-prop-angle-label, +.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-prop-slope, +.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-prop-slope-label, +.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-prop-update, +.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-remove { + display: table-cell; + width: 50% +} + +.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-prop-name, +.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-prop-tail, +.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-prop-angle { + padding-right: 5px; +} + +.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row select, +.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row input { + float: right; + width: 50%; +} + +.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-remove { + vertical-align: bottom; +} + +.vectordraw_edit_block .menu .vector-properties .vector-prop-list .row .vector-remove button { + float: right; +} diff --git a/vectordraw/public/js/vectordraw.js b/vectordraw/public/js/vectordraw.js index 8c5be66..30716f7 100644 --- a/vectordraw/public/js/vectordraw.js +++ b/vectordraw/public/js/vectordraw.js @@ -16,6 +16,8 @@ function VectorDrawXBlock(runtime, element, init_args) { this.element.on('click', '.add-vector', this.addElementFromList.bind(this)); this.element.on('click', '.undo', this.undo.bind(this)); this.element.on('click', '.redo', this.redo.bind(this)); + this.element.on('change', '.menu .element-list-edit', this.onEditStart.bind(this)); + this.element.on('click', '.menu .vector-prop-update', this.onEditSubmit.bind(this)); // Prevents default image drag and drop actions in some browsers. this.element.on('mousedown', '.jxgboard image', function(evt) { evt.preventDefault(); }); @@ -23,6 +25,7 @@ function VectorDrawXBlock(runtime, element, init_args) { }; VectorDraw.prototype.render = function() { + $('.vector-prop-slope', this.element).hide(); // Assign the jxgboard element a random unique ID, // because JXG.JSXGraph.initBoard needs it. this.element.find('.jxgboard').prop('id', _.uniqueId('jxgboard')); @@ -52,7 +55,8 @@ function VectorDrawXBlock(runtime, element, init_args) { function drawBackground(bg, ratio) { var height = (bg.height) ? bg.height : bg.width * ratio; var coords = (bg.coords) ? bg.coords : [-bg.width/2, -height/2]; - self.board.create('image', [bg.src, coords, [bg.width, height]], {fixed: true}); + var image = self.board.create('image', [bg.src, coords, [bg.width, height]], {fixed: true}); + $(image.rendNode).attr('alt', bg.description); } if (this.settings.background) { @@ -64,9 +68,9 @@ function VectorDrawXBlock(runtime, element, init_args) { } } - var noOptionSelected = true; + var noAddOptionSelected = true; - function renderOrEnableOption(element, idx, type, board) { + function renderAndSetMenuOptions(element, idx, type, board) { if (element.render) { if (type === 'point') { board.renderPoint(idx); @@ -74,25 +78,39 @@ function VectorDrawXBlock(runtime, element, init_args) { board.renderVector(idx); } } else { - // Enable corresponding option in menu ... - var option = board.getMenuOption(type, idx); - option.prop('disabled', false); + // Enable corresponding option in menu for adding vectors ... + var addOption = board.getAddMenuOption(type, idx); + addOption.prop('disabled', false); // ... and select it if no option is currently selected - if (noOptionSelected) { - option.prop('selected', true); - noOptionSelected = false; + if (noAddOptionSelected) { + addOption.prop('selected', true); + noAddOptionSelected = false; } + // Disable corresponding option in menu for editing vectors + var editOption = board.getEditMenuOption(type, idx); + editOption.prop('disabled', true); } } + // a11y + + // Generate and set unique ID for "Vector Properties"; + // this is necessary to ensure that "aria-describedby" associations + // between vectors and the "Vector Properties" box don't break + // when multiple boards are present: + var vectorProperties = $(".vector-properties", element); + vectorProperties.attr("id", id + "-vector-properties"); + + // Draw vectors and points this.settings.points.forEach(function(point, idx) { - renderOrEnableOption(point, idx, 'point', this); + renderAndSetMenuOptions(point, idx, 'point', this); }, this); this.settings.vectors.forEach(function(vec, idx) { - renderOrEnableOption(vec, idx, 'vector', this); + renderAndSetMenuOptions(vec, idx, 'vector', this); }, this); + // Set up event handlers this.board.on('down', this.onBoardDown.bind(this)); this.board.on('move', this.onBoardMove.bind(this)); this.board.on('up', this.onBoardUp.bind(this)); @@ -110,7 +128,7 @@ function VectorDrawXBlock(runtime, element, init_args) { this.board.create('point', coords, point.style); if (!point.fixed) { // Disable the