MDL-47494 ddmarker: NOBUG implemented the highlighting of drop zones with no drag hit

This commit is contained in:
Jamie Pratt
2011-11-17 13:20:43 +07:00
parent bcbe321a87
commit e47c7eaec2
6 changed files with 254 additions and 147 deletions
@@ -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.<br />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}.';
+27
View File
@@ -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) {
+26 -2
View File
@@ -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;
}
+26 -1
View File
@@ -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;
+165 -7
View File
@@ -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('<span class="'+classnames+'"><a href="#">' +
markertext+'</a></span>');
}
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']
});
+9 -137
View File
@@ -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('<span class="'+classnames+'">' +
markertext+'</span>');
}
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'), '');