From e47c7eaec2bdeea05f918e63e130c76906c20d1a Mon Sep 17 00:00:00 2001 From: Jamie Pratt Date: Thu, 17 Nov 2011 13:20:43 +0700 Subject: [PATCH] MDL-47494 ddmarker: NOBUG implemented the highlighting of drop zones with no drag hit --- .../type/ddmarker/lang/en/qtype_ddmarker.php | 1 + question/type/ddmarker/question.php | 27 +++ question/type/ddmarker/renderer.php | 28 ++- question/type/ddmarker/styles.css | 27 ++- question/type/ddmarker/yui/dd/dd.js | 172 +++++++++++++++++- question/type/ddmarker/yui/form/form.js | 146 +-------------- 6 files changed, 254 insertions(+), 147 deletions(-) diff --git a/question/type/ddmarker/lang/en/qtype_ddmarker.php b/question/type/ddmarker/lang/en/qtype_ddmarker.php index 4e92ba4efdb..821416ba40f 100644 --- a/question/type/ddmarker/lang/en/qtype_ddmarker.php +++ b/question/type/ddmarker/lang/en/qtype_ddmarker.php @@ -41,6 +41,7 @@ $string['draggableword'] = 'Draggable text'; $string['dropzone'] = 'Drop zone {$a}'; $string['dropzoneheader'] = 'Drop zones'; $string['editingddmarker'] = 'Editing drag and drop markers'; +$string['followingarewrong'] = 'The following markers have not been dragged to the correct drop zone : {$a}. The drop zones they should have been dragged to are shown above.
Click on the markers above to see the drop zones they should have been dropped in highlighted.'; $string['formerror_nobgimage'] = 'You need to select an image to use as the background for the drag and drop area.'; $string['formerror_noitemselected'] = 'You have specified a drop zone but not chosen a marker that must be dragged to the zone'; $string['formerror_nosemicolons'] = 'There are no semicolons in your coordinates string. Your coordinates for a {$a->shape} should be expressed as - {$a->coordsstring}.'; diff --git a/question/type/ddmarker/question.php b/question/type/ddmarker/question.php index 104429ec36d..6611979d783 100644 --- a/question/type/ddmarker/question.php +++ b/question/type/ddmarker/question.php @@ -259,7 +259,34 @@ class qtype_ddmarker_question extends qtype_ddtoimage_question_base { } return $cleanedresponse; } + public function get_drop_zones_without_hit(array $response) { + $hits = $this->choose_hits($response); + $nohits = array(); + foreach ($this->places as $placeno => $place) { + $choice = $this->get_right_choice_for($placeno); + if (!isset($hits[$placeno])) { + $nohit = new stdClass(); + $nohit->coords = $place->coords; + $nohit->shape = $place->shape->name(); + $nohit->markertext = $this->choices[1][$choice]->text; + $nohits[] = $nohit; + } + } + return $nohits; + } + public function wrong_parts(array $response) { + $hits = $this->choose_hits($response); + + $wrong = array(); + foreach ($this->places as $placeno => $place) { + $choice = $this->get_right_choice_for($placeno); + if (!isset($hits[$placeno])) { + $wrong[] = $this->choices[1][$choice]->text; + } + } + return $wrong; + } public function classify_response(array $response) { $parts = array(); foreach ($this->places as $place => $group) { diff --git a/question/type/ddmarker/renderer.php b/question/type/ddmarker/renderer.php index b74d1f2ac98..2352d7090b9 100644 --- a/question/type/ddmarker/renderer.php +++ b/question/type/ddmarker/renderer.php @@ -76,10 +76,23 @@ class qtype_ddmarker_renderer extends qtype_ddtoimage_renderer_base { if ($options->readonly) { $dragitemsclass .= ' readonly'; } + $dragitems = html_writer::tag('div', $draghomes, array('class'=> $dragitemsclass)); - $output .= html_writer::tag('div', $droparea.$dragitems, array('class'=>'ddarea')); + $dropzones = html_writer::empty_tag('div', array('class'=> 'dropzones')); + $texts = html_writer::empty_tag('div', array('class'=> 'markertexts')); + $output .= html_writer::tag('div', + $droparea.$dragitems.$dropzones.$texts, + array('class'=>'ddarea')); + + if ($qa->get_state()->is_finished()) { + $visibledropzones = $question->get_drop_zones_without_hit($response); + } else { + $visibledropzones = array(); + } + $topnode = 'div#q'.$qa->get_slot(); - $params = array('topnode' => $topnode, + $params = array('dropzones' => $visibledropzones, + 'topnode' => $topnode, 'readonly' => $options->readonly); $PAGE->requires->yui_module('moodle-qtype_ddmarker-dd', @@ -91,6 +104,17 @@ class qtype_ddmarker_renderer extends qtype_ddtoimage_renderer_base { $question->get_validation_error($qa->get_last_qt_data()), array('class' => 'validationerror')); } + if ($qa->get_state()->is_finished()) { + $wrongparts = $question->wrong_parts($response); + $wrongpartsstring = ''; + foreach($wrongparts as $wrongpart) { + $wrongpartsstring .= html_writer::nonempty_tag('span', + $wrongpart, array('class' => 'wrongpart')); + } + $output .= html_writer::nonempty_tag('span', + get_string('followingarewrong', 'qtype_ddmarker', $wrongpartsstring), + array('class' => 'wrongparts')); + } $output .= html_writer::tag('div', $hiddenfields, array('class'=>'ddform')); return $output; } diff --git a/question/type/ddmarker/styles.css b/question/type/ddmarker/styles.css index f2aeea5e5f2..a19547e4265 100644 --- a/question/type/ddmarker/styles.css +++ b/question/type/ddmarker/styles.css @@ -51,9 +51,10 @@ form.mform fieldset#previewareaheader div.draghome, form.mform fieldset#previewa *font:x-small; /* for IE in quirks mode */ } .que.ddmarker div.dragitems span.markertext, +.que.ddmarker div.dropzones span.markertext, form.mform fieldset#previewareaheader div.markertexts span.markertext { margin: 5px; - z-index: 2; + z-index: 3; background-color: white; border-style : none; padding: 5px; @@ -68,6 +69,30 @@ form.mform fieldset#previewareaheader div.markertexts span.markertext { -khtml-border-radius: 10px; border-radius: 10px; } +.que.ddmarker div.dropzones span.markertext { + z-index: 2; + background-color: yellow; + border-style : solid; + border-width : 2px; + border-colour : khaki; +} +.que.ddmarker span.wrongparts span.wrongpart { + background-color: yellow; + border-style : solid; + border-width : 2px; + border-colour : khaki; + padding: 5px; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + -khtml-border-radius: 10px; + border-radius: 10px; + -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=60)"; + filter: alpha(opacity=60); + -moz-opacity:0.6; + -khtml-opacity: 0.6; + opacity: 0.6; + margin: 5px; +} .que.ddmarker div.dragitems img.target { position:absolute; left:-7px; diff --git a/question/type/ddmarker/yui/dd/dd.js b/question/type/ddmarker/yui/dd/dd.js index 98a5ac60834..09157d1007c 100644 --- a/question/type/ddmarker/yui/dd/dd.js +++ b/question/type/ddmarker/yui/dd/dd.js @@ -10,6 +10,7 @@ YUI.add('moodle-qtype_ddmarker-dd', function(Y) { doc : null, polltimer : null, afterimageloaddone : false, + graphics : null, poll_for_image_load : function (e, waitforimageconstrain, pause, doafterwords) { if (this.afterimageloaddone) { return; @@ -93,6 +94,9 @@ YUI.add('moodle-qtype_ddmarker-dd', function(Y) { }, input_for_choice : function (choiceno) { return topnode.one('input.choice'+choiceno); + }, + marker_texts : function () { + return topnode.one('div.markertexts'); } } @@ -115,6 +119,148 @@ YUI.add('moodle-qtype_ddmarker-dd', function(Y) { convert_to_window_xy : function (bgimgxy) { return [+bgimgxy[0] + this.doc.bg_img().getX() + 1, +bgimgxy[1] + this.doc.bg_img().getY() + 1]; + }, + shapes : [], + draw_drop_zone : function (dropzoneno, markertext, shape, coords, colour) { + var existingmarkertext = this.doc.marker_texts().one('span.markertext'+dropzoneno+' a'); + if (existingmarkertext) { + if (markertext !== '') { + existingmarkertext.setContent(markertext); + } else { + existingmarkertext.remove(true); + } + } else if (markertext !== '') { + var classnames = 'markertext markertext' + dropzoneno; + this.doc.marker_texts().append('' + + markertext+''); + } + var drawfunc = 'draw_shape_'+shape; + if (this[drawfunc] instanceof Function){ + var xyfortext = this[drawfunc](dropzoneno, coords, colour); + if (xyfortext !== null) { + var markerspan = Y.one('div.ddarea div.markertexts span.markertext'+dropzoneno); + if (markerspan !== null) { + markerspan.setStyle('opacity', '0.6'); + xyfortext[0] -= Math.round(markerspan.get('offsetWidth') / 2); + xyfortext[1] -= Math.round(markerspan.get('offsetHeight') / 2); + markerspan.setXY(this.convert_to_window_xy(xyfortext)); + markerspan.one('a').once('click', + function (e, dropzoneno) { + var fill = this.shapes[dropzoneno].get('fill'); + fill.opacity = 1; + this.shapes[dropzoneno].set('fill', fill); + }, + this, + dropzoneno + ); + markerspan.one('a').set('tabIndex', 0); + + } + } + } + }, + draw_shape_circle : function (dropzoneno, coords, colour) { + var coordsparts = coords.match(/(\d+),(\d+);(\d+)/); + if (coordsparts && coordsparts.length === 4) { + var xy = [+coordsparts[1] - coordsparts[3], +coordsparts[2] - coordsparts[3]]; + if (this.coords_in_img(xy)) { + var widthheight = [+coordsparts[3]*2, +coordsparts[3]*2]; + var shape = this.graphics.addShape({ + type: 'circle', + width: widthheight[0], + height: widthheight[1], + fill: { + color: colour, + opacity : "0.5" + }, + stroke: { + weight:1, + color: "black" + } + }); + shape.setXY(this.convert_to_window_xy(xy)); + this.shapes[dropzoneno] = shape; + return [+coordsparts[1], +coordsparts[2]]; + } + } + return null; + }, + draw_shape_rectangle : function (dropzoneno, coords, colour) { + var coordsparts = coords.match(/(\d+),(\d+);(\d+),(\d+)/); + if (coordsparts && coordsparts.length === 5) { + var xy = [+coordsparts[1], +coordsparts[2]]; + var widthheight = [+coordsparts[3], +coordsparts[4]]; + if (this.coords_in_img([xy[0]+widthheight[0], xy[1]+widthheight[1]])) { + var shape = this.graphics.addShape({ + type: 'rect', + width: widthheight[0], + height: widthheight[1], + fill: { + color: colour, + opacity : "0.5" + }, + stroke: { + weight:1, + color: "black" + } + }); + shape.setXY(this.convert_to_window_xy(xy)); + this.shapes[dropzoneno] = shape; + return [+xy[0]+widthheight[0]/2, +xy[1]+widthheight[1]/2]; + } + } + return null; + + }, + draw_shape_polygon : function (dropzoneno, coords, colour) { + var coordsparts = coords.split(';'); + var xy = []; + for (var i in coordsparts) { + var parts = coordsparts[i].match(/^(\d+),(\d+)$/); + if (parts !== null && this.coords_in_img([parts[1], parts[2]])) { + xy[xy.length] = [parts[1], parts[2]]; + } + } + if (xy.length > 2) { + var polygon = this.graphics.addShape({ + type: "path", + stroke: { + weight: 1, + color: "black" + }, + fill: { + color: colour, + opacity : "0.5" + } + }); + var maxxy = [0,0]; + var minxy = [this.doc.bg_img().get('width'), this.doc.bg_img().get('height')]; + for (var i in xy) { + //calculate min and max points to find center to show marker on + minxy[0] = Math.min(xy[i][0], minxy[0]); + minxy[1] = Math.min(xy[i][1], minxy[1]); + maxxy[0] = Math.max(xy[i][0], maxxy[0]); + maxxy[1] = Math.max(xy[i][1], maxxy[1]); + if (i == 0) { + polygon.moveTo(xy[i][0], xy[i][1]); + } else { + polygon.lineTo(xy[i][0], xy[i][1]); + } + } + if (+xy[0][0] !== +xy[xy.length-1][0] || +xy[0][1] !== +xy[xy.length-1][1]) { + var windowxy = this.convert_to_window_xy(xy[0]); + polygon.lineTo(xy[0][0], xy[0][1]); //close polygon if not already closed + } + polygon.end(); + polygon.setXY(this.doc.bg_img().getXY()); + this.shapes[dropzoneno] = polygon; + return [Math.round((minxy[0] + maxxy[0])/2), Math.round((minxy[1] + maxxy[1])/2)]; + } + return null; + }, + coords_in_img : function (coords) { + return (coords[0] <= this.doc.bg_img().get('width') && + coords[1] <= this.doc.bg_img().get('height')); } }, { NAME : DDMARKERDDNAME, @@ -142,8 +288,8 @@ YUI.add('moodle-qtype_ddmarker-dd', function(Y) { false, 0, this.after_image_load); }, after_image_load : function () { - this.reposition_drags(); - Y.later(500, this, this.reposition_drags, [], true); + this.redraw_drags_and_drops(); + Y.later(2000, this, this.redraw_drags_and_drops, [], true); }, clone_new_drag_item : function (draghome, itemno) { var drag = draghome.cloneNode(true); @@ -170,13 +316,13 @@ YUI.add('moodle-qtype_ddmarker-dd', function(Y) { dragnode.removeClass('item'+dragnode); } this.save_all_xy_for_choice(choiceno, null); - this.reposition_drags(); + this.redraw_drags_and_drops(); }, this); dd.after('drag:end', function(e) { var dragnode = e.target.get('node'); var choiceno = this.get_choiceno_for_node(dragnode); this.save_all_xy_for_choice(choiceno, dragnode); - this.reposition_drags(); + this.redraw_drags_and_drops(); }, this); //--- keyboard accessibility drag.set('tabIndex', 0); @@ -236,7 +382,7 @@ YUI.add('moodle-qtype_ddmarker-dd', function(Y) { return [Math.round(+windowxy[0] - this.doc.bg_img().getX()-1), Math.round(+windowxy[1] - this.doc.bg_img().getY()-1)]; }, - reposition_drags : function() { + redraw_drags_and_drops : function() { this.doc.drag_items().each(function(item) { //if (!item.hasClass('yui3-dd-dragging')){ item.addClass('unneeded'); @@ -262,6 +408,18 @@ YUI.add('moodle-qtype_ddmarker-dd', function(Y) { item.remove(true); } }, this); + if (this.graphics !== null) { + this.graphics.destroy(); + } + this.restart_colours(); + this.graphics = new Y.Graphic( + {render:this.doc.top_node().one("div.ddarea div.dropzones")} + ); + for (var dropzoneno in this.get('dropzones')) { + var colourfordropzone = this.get_next_colour(); + var d = this.get('dropzones')[dropzoneno]; + this.draw_drop_zone(dropzoneno, d.markertext, d.shape, d.coords, colourfordropzone); + } }, /** * Return coords of all drag items except any that are currently being dragged @@ -326,7 +484,7 @@ YUI.add('moodle-qtype_ddmarker-dd', function(Y) { } - }, {NAME : DDMARKERQUESTIONNAME, ATTRS : {}}); + }, {NAME : DDMARKERQUESTIONNAME, ATTRS : {dropzones:{value:[]}}}); Y.Event.define('dragchange', { // Webkit and IE repeat keydown when you hold down arrow keys. @@ -360,5 +518,5 @@ YUI.add('moodle-qtype_ddmarker-dd', function(Y) { return new DDMARKER_QUESTION(config); } }, '@VERSION@', { - requires:['node', 'event-resize', 'dd', 'dd-drop', 'dd-constrain'] + requires:['node', 'event-resize', 'dd', 'dd-drop', 'dd-constrain', 'graphics'] }); diff --git a/question/type/ddmarker/yui/form/form.js b/question/type/ddmarker/yui/form/form.js index d7bf466efd8..7023e17b45b 100644 --- a/question/type/ddmarker/yui/form/form.js +++ b/question/type/ddmarker/yui/form/form.js @@ -55,7 +55,6 @@ YUI.add('moodle-qtype_ddmarker-form', function(Y) { e.target.detach('load', this.constrain_image_size); }, - graphics : null, update_drop_zones : function () { @@ -66,146 +65,19 @@ YUI.add('moodle-qtype_ddmarker-form', function(Y) { } this.restart_colours(); this.graphics = new Y.Graphic({render:"div.ddarea div.dropzones"}); - for (var i=0; i < this.form.get_form_value('nodropzone', []); i++) { - this.update_drop_zone(i); - } - }, - update_drop_zone : function (dropzoneno) { - var dragitemno = this.form.get_form_value('drops', [dropzoneno, 'choice']); - var markertext = this.get_marker_text(dragitemno); - var existingmarkertext = Y.one('div.ddarea div.markertexts span.markertext'+dropzoneno); - if (existingmarkertext) { - if (markertext !== '') { - existingmarkertext.setContent(markertext); - } else { - existingmarkertext.remove(true); - } - } else if (markertext !== '') { - var classnames = 'markertext markertext' + dropzoneno; - Y.one('div.ddarea div.markertexts').append('' + - markertext+''); - } - var shape = this.form.get_form_value('drops', [dropzoneno, 'shape']); - var drawfunc = 'draw_shape_'+shape; - var colourfordropzone = this.get_next_colour(); - Y.one('input#id_drops_'+dropzoneno+'_coords') + var noofdropzones = this.form.get_form_value('nodropzone', []); + for (var dropzoneno=0; dropzoneno < noofdropzones; dropzoneno++) { + var dragitemno = this.form.get_form_value('drops', [dropzoneno, 'choice']); + var markertext = this.get_marker_text(dragitemno); + var shape = this.form.get_form_value('drops', [dropzoneno, 'shape']); + var coords = this.get_coords(dropzoneno); + var colourfordropzone = this.get_next_colour(); + Y.one('input#id_drops_'+dropzoneno+'_coords') .setStyle('background-color', colourfordropzone); - if (this[drawfunc] instanceof Function){ - var xyfortext = this[drawfunc](dropzoneno, colourfordropzone); - if (xyfortext !== null) { - var markerspan = Y.one('div.ddarea div.markertexts span.markertext'+dropzoneno); - if (markerspan !== null) { - markerspan.setStyle('opacity', '0.6'); - xyfortext[0] -= Math.round(markerspan.get('offsetWidth') / 2); - xyfortext[1] -= Math.round(markerspan.get('offsetHeight') / 2); - markerspan.setXY(this.convert_to_window_xy(xyfortext)); - } - } + this.draw_drop_zone(dropzoneno, markertext, shape, coords, colourfordropzone); } }, - draw_shape_circle : function (dropzoneno, colourfordropzone) { - var coords = this.get_coords(dropzoneno); - var coordsparts = coords.match(/(\d+),(\d+);(\d+)/); - if (coordsparts && coordsparts.length === 4) { - var xy = [+coordsparts[1] - coordsparts[3], +coordsparts[2] - coordsparts[3]]; - if (this.coords_in_img(xy)) { - var widthheight = [+coordsparts[3]*2, +coordsparts[3]*2]; - var shape = this.graphics.addShape({ - type: 'circle', - width: widthheight[0], - height: widthheight[1], - fill: { - color: colourfordropzone, - opacity : "0.5" - }, - stroke: { - weight:1, - color: "black" - } - }); - shape.setXY(this.convert_to_window_xy(xy)); - return [+coordsparts[1], +coordsparts[2]]; - } - } - return null; - }, - draw_shape_rectangle : function (dropzoneno, colourfordropzone) { - var coords = this.get_coords(dropzoneno); - var coordsparts = coords.match(/(\d+),(\d+);(\d+),(\d+)/); - if (coordsparts && coordsparts.length === 5) { - var xy = [+coordsparts[1], +coordsparts[2]]; - var widthheight = [+coordsparts[3], +coordsparts[4]]; - if (this.coords_in_img([xy[0]+widthheight[0], xy[1]+widthheight[1]])) { - var shape = this.graphics.addShape({ - type: 'rect', - width: widthheight[0], - height: widthheight[1], - fill: { - color: colourfordropzone, - opacity : "0.5" - }, - stroke: { - weight:1, - color: "black" - } - }); - shape.setXY(this.convert_to_window_xy(xy)); - return [+xy[0]+widthheight[0]/2, +xy[1]+widthheight[1]/2]; - } - } - return null; - }, - draw_shape_polygon : function (dropzoneno, colourfordropzone) { - var coords = this.form.get_form_value('drops', [dropzoneno, 'coords']); - var coordsparts = coords.split(';'); - var xy = []; - for (var i in coordsparts) { - var parts = coordsparts[i].match(/^(\d+),(\d+)$/); - if (parts !== null && this.coords_in_img([parts[1], parts[2]])) { - xy[xy.length] = [parts[1], parts[2]]; - } - } - if (xy.length > 2) { - var polygon = this.graphics.addShape({ - type: "path", - stroke: { - weight: 1, - color: "black" - }, - fill: { - color: colourfordropzone, - opacity : "0.5" - } - }); - var maxxy = [0,0]; - var minxy = [this.doc.bg_img().get('width'), this.doc.bg_img().get('height')]; - for (var i in xy) { - //calculate min and max points to find center to show marker on - minxy[0] = Math.min(xy[i][0], minxy[0]); - minxy[1] = Math.min(xy[i][1], minxy[1]); - maxxy[0] = Math.max(xy[i][0], maxxy[0]); - maxxy[1] = Math.max(xy[i][1], maxxy[1]); - if (i == 0) { - polygon.moveTo(xy[i][0], xy[i][1]); - } else { - polygon.lineTo(xy[i][0], xy[i][1]); - } - } - if (+xy[0][0] !== +xy[xy.length-1][0] || +xy[0][1] !== +xy[xy.length-1][1]) { - var windowxy = this.convert_to_window_xy(xy[0]); - polygon.lineTo(xy[0][0], xy[0][1]); //close polygon if not already closed - } - polygon.end(); - polygon.setXY(this.doc.bg_img().getXY()); - return [Math.round((minxy[0] + maxxy[0])/2), Math.round((minxy[1] + maxxy[1])/2)]; - } - return null; - }, - coords_in_img : function (coords) { - return (coords[0] <= this.doc.bg_img().get('width') && - coords[1] <= this.doc.bg_img().get('height')); - }, get_coords : function (dropzoneno) { var coords = this.form.get_form_value('drops', [dropzoneno, 'coords']); return coords.replace(new RegExp("\\s*", 'g'), '');