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'), '');