MDL-47494 ddmarker: Convert the JavaScript to use Shifter. #13314

This commit is contained in:
M Kassaei 2015-01-29 17:35:02 +00:00 committed by Tim Hunt
parent a366c8b950
commit 24c6f2fc8c
14 changed files with 2546 additions and 804 deletions

View file

@ -0,0 +1,565 @@
YUI.add('moodle-qtype_ddmarker-dd', function (Y, NAME) {
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
var DDMARKERDDNAME = 'moodle-qtype_ddmarker-dd';
var DDMARKER_DD = function() {
DDMARKER_DD.superclass.constructor.apply(this, arguments);
};
/**
* This is the base class for the question rendering and question editing form code.
*/
Y.extend(DDMARKER_DD, Y.Base, {
doc : null,
polltimer : null,
afterimageloaddone : false,
graphics : null,
poll_for_image_load : function (e, waitforimageconstrain, pause, doafterwords) {
if (this.afterimageloaddone) {
return;
}
var bgdone = this.doc.bg_img().get('complete');
if (waitforimageconstrain) {
bgdone = bgdone && this.doc.bg_img().hasClass('constrained');
}
if (bgdone) {
if (this.polltimer !== null) {
this.polltimer.cancel();
this.polltimer = null;
}
this.doc.bg_img().detach('load', this.poll_for_image_load);
if (pause !== 0) {
Y.later(pause, this, doafterwords);
} else {
doafterwords.call(this);
}
this.afterimageloaddone = true;
} else if (this.polltimer === null) {
var pollarguments = [null, waitforimageconstrain, pause, doafterwords];
this.polltimer =
Y.later(1000, this, this.poll_for_image_load, pollarguments, true);
}
},
/**
* Object to encapsulate operations on dd area.
*/
doc_structure : function () {
var topnode = Y.one(this.get('topnode'));
var dragitemsarea = topnode.one('div.dragitems');
var dropbgarea = topnode.one('div.droparea');
return {
top_node : function() {
return topnode;
},
bg_img : function() {
return topnode.one('.dropbackground');
},
load_bg_img : function (url) {
dropbgarea.setContent('<img class="dropbackground" src="' + url + '"/>');
this.bg_img().on('load', this.on_image_load, this, 'bg_image');
},
drag_items : function() {
return dragitemsarea.all('.dragitem');
},
drag_items_for_choice : function(choiceno) {
return dragitemsarea.all('span.dragitem.choice' + choiceno);
},
drag_item_for_choice : function(choiceno, itemno) {
return dragitemsarea.one('span.dragitem.choice' + choiceno +
'.item' + itemno);
},
drag_item_being_dragged : function(choiceno) {
return dragitemsarea.one('span.dragitem.beingdragged.choice' + choiceno);
},
drag_item_home : function (choiceno) {
return dragitemsarea.one('span.draghome.choice' + choiceno);
},
drag_item_homes : function() {
return dragitemsarea.all('span.draghome');
},
get_classname_numeric_suffix : function(node, prefix) {
var classes = node.getAttribute('class');
if (classes !== '') {
var classesarr = classes.split(' ');
for (var index = 0; index < classesarr.length; index++) {
var patt1 = new RegExp('^' + prefix + '([0-9])+$');
if (patt1.test(classesarr[index])) {
var patt2 = new RegExp('([0-9])+$');
var match = patt2.exec(classesarr[index]);
return Number(match[0]);
}
}
}
return null;
},
inputs_for_choices : function () {
return topnode.all('input.choices');
},
input_for_choice : function (choiceno) {
return topnode.one('input.choice' + choiceno);
},
marker_texts : function () {
return topnode.one('div.markertexts');
}
};
},
colours : ['#FFFFFF', '#B0C4DE', '#DCDCDC', '#D8BFD8',
'#87CEFA','#DAA520', '#FFD700', '#F0E68C'],
nextcolourindex : 0,
restart_colours : function () {
this.nextcolourindex = 0;
},
get_next_colour : function () {
var colour = this.colours[this.nextcolourindex];
this.nextcolourindex++;
if (this.nextcolourindex === this.colours.length) {
this.nextcolourindex = 0;
}
return colour;
},
convert_to_window_xy : function (bgimgxy) {
return [Number(bgimgxy[0]) + this.doc.bg_img().getX() + 1,
Number(bgimgxy[1]) + this.doc.bg_img().getY() + 1];
},
shapes : [],
draw_drop_zone : function (dropzoneno, markertext, shape, coords, colour, link) {
var existingmarkertext;
if (link) {
existingmarkertext = this.doc.marker_texts().one('span.markertext' + dropzoneno + ' a');
} else {
existingmarkertext = this.doc.marker_texts().one('span.markertext' + dropzoneno);
}
if (existingmarkertext) {
if (markertext !== '') {
existingmarkertext.setContent(markertext);
} else {
existingmarkertext.remove(true);
}
} else if (markertext !== '') {
var classnames = 'markertext markertext' + dropzoneno;
if (link) {
this.doc.marker_texts().append('<span class="' + classnames + '"><a href="#">' +
markertext + '</a></span>');
} else {
this.doc.marker_texts().append('<span class="' + classnames + '">' +
markertext + '</span>');
}
}
var drawfunc = 'draw_shape_' + shape;
if (this[drawfunc] instanceof Function){
var xyfortext = this[drawfunc](dropzoneno, coords, colour);
if (xyfortext !== null) {
var markerspan = this.doc.top_node().one('div.ddarea div.markertexts span.markertext' + dropzoneno);
if (markerspan !== null) {
markerspan.setStyle('opacity', '0.6');
xyfortext[0] -= markerspan.get('offsetWidth') / 2;
xyfortext[1] -= markerspan.get('offsetHeight') / 2;
markerspan.setXY(this.convert_to_window_xy(xyfortext));
var markerspananchor = markerspan.one('a');
if (markerspananchor !== null) {
markerspananchor.once('click', function (e, dropzoneno) {
var fill = this.shapes[dropzoneno].get('fill');
fill.opacity = 1;
this.shapes[dropzoneno].set('fill', fill);
},
this,
dropzoneno
);
markerspananchor.set('tabIndex', 0);
}
}
}
}
},
draw_shape_circle : function (dropzoneno, coords, colour) {
var coordsparts = coords.match(/(\d+),(\d+);(\d+)/);
if (coordsparts && coordsparts.length === 4) {
var xy = [Number(coordsparts[1]) - coordsparts[3], Number(coordsparts[2]) - coordsparts[3]];
if (this.coords_in_img(xy)) {
var widthheight = [Number(coordsparts[3]) * 2, Number(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 [Number(coordsparts[1]), Number(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 = [Number(coordsparts[1]), Number(coordsparts[2])];
var widthheight = [Number(coordsparts[3]), Number(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 [Number(xy[0]) + widthheight[0] / 2, Number(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 (i = 0; i < xy.length; i++) {
//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 (Number(xy[0][0]) !== Number(xy[xy.length - 1][0]) || Number(xy[0][1]) !== Number(xy[xy.length - 1][1])) {
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 [(minxy[0] + maxxy[0]) / 2, (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,
ATTRS : {
drops : {value : null},
readonly : {value : false},
topnode : {value : null}
}
});
M.qtype_ddmarker = M.qtype_ddmarker || {};
M.qtype_ddmarker.dd_base_class = DDMARKER_DD;
var DDMARKERQUESTIONNAME = 'ddmarker_question';
var DDMARKER_QUESTION = function() {
DDMARKER_QUESTION.superclass.constructor.apply(this, arguments);
};
/**
* This is the code for question rendering.
*/
Y.extend(DDMARKER_QUESTION, M.qtype_ddmarker.dd_base_class, {
initializer : function() {
this.doc = this.doc_structure(this);
this.poll_for_image_load(null, false, 0, this.after_image_load);
this.doc.bg_img().after('load', this.poll_for_image_load, this,
false, 0, this.after_image_load);
},
after_image_load : function () {
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);
drag.removeClass('draghome');
drag.addClass('dragitem');
drag.addClass('item' + itemno);
drag.one('span.markertext').setStyle('opacity', 0.6);
draghome.insert(drag, 'after');
if (!this.get('readonly')) {
this.draggable(drag);
}
return drag;
},
draggable : function (drag) {
var dd = new Y.DD.Drag({
node: drag,
dragMode: 'intersect'
}).plug(Y.Plugin.DDConstrained, {constrain2node: this.doc.top_node()});
dd.after('drag:start', function(e){
var dragnode = e.target.get('node');
dragnode.addClass('beingdragged');
var choiceno = this.get_choiceno_for_node(dragnode);
var itemno = this.get_itemno_for_node(dragnode);
if (itemno !== null) {
dragnode.removeClass('item' + dragnode);
}
this.save_all_xy_for_choice(choiceno, null);
this.redraw_drags_and_drops();
}, this);
dd.after('drag:end', function(e) {
var dragnode = e.target.get('node');
dragnode.removeClass('beingdragged');
var choiceno = this.get_choiceno_for_node(dragnode);
this.save_all_xy_for_choice(choiceno, dragnode);
this.redraw_drags_and_drops();
}, this);
//--- keyboard accessibility
drag.set('tabIndex', 0);
drag.on('dragchange', this.drop_zone_key_press, this);
},
save_all_xy_for_choice: function (choiceno, dropped) {
var coords = [];
var bgimgxy;
for (var i = 0; i <= this.doc.drag_items_for_choice(choiceno).size(); i++) {
var dragitem = this.doc.drag_item_for_choice(choiceno, i);
if (dragitem) {
dragitem.removeClass('item' + i);
if (!dragitem.hasClass('beingdragged')) {
bgimgxy = this.convert_to_bg_img_xy(dragitem.getXY());
if (this.xy_in_bgimg(bgimgxy)) {
dragitem.removeClass('item' + i);
dragitem.addClass('item' + coords.length);
coords[coords.length] = bgimgxy;
}
}
}
}
if (dropped !== null){
bgimgxy = this.convert_to_bg_img_xy(dropped.getXY());
dropped.addClass('item' + coords.length);
if (this.xy_in_bgimg(bgimgxy)) {
coords[coords.length] = bgimgxy;
}
}
this.set_form_value(choiceno, coords.join(';'));
},
reset_drag_xy : function (choiceno) {
this.set_form_value(choiceno, '');
},
set_form_value : function (choiceno, value) {
this.doc.input_for_choice(choiceno).set('value', value);
},
//make sure xy value is not out of bounds of bg image
xy_in_bgimg : function (bgimgxy) {
if ((bgimgxy[0] < 0) ||
(bgimgxy[1] < 0) ||
(bgimgxy[0] > this.doc.bg_img().get('width')) ||
(bgimgxy[1] > this.doc.bg_img().get('height'))){
return false;
} else {
return true;
}
},
constrain_to_bgimg : function (windowxy) {
var bgimgxy = this.convert_to_bg_img_xy(windowxy);
bgimgxy[0] = Math.max(0, bgimgxy[0]);
bgimgxy[1] = Math.max(0, bgimgxy[1]);
bgimgxy[0] = Math.min(this.doc.bg_img().get('width'), bgimgxy[0]);
bgimgxy[1] = Math.min(this.doc.bg_img().get('height'), bgimgxy[1]);
return this.convert_to_window_xy(bgimgxy);
},
convert_to_bg_img_xy : function (windowxy) {
return [Number(windowxy[0]) - this.doc.bg_img().getX() - 1,
Number(windowxy[1]) - this.doc.bg_img().getY() - 1];
},
redraw_drags_and_drops : function() {
this.doc.drag_items().each(function(item) {
//if (!item.hasClass('beingdragged')){
item.addClass('unneeded');
//}
}, this);
this.doc.inputs_for_choices().each(function (input) {
var choiceno = this.get_choiceno_for_node(input);
var coords = this.get_coords(input);
var dragitemhome = this.doc.drag_item_home(choiceno);
for (var i = 0; i < coords.length; i++) {
var dragitem = this.doc.drag_item_for_choice(choiceno, i);
if (!dragitem || dragitem.hasClass('beingdragged')) {
dragitem = this.clone_new_drag_item(dragitemhome, i);
} else {
dragitem.removeClass('unneeded');
}
dragitem.setXY(coords[i]);
}
}, this);
this.doc.drag_items().each(function(item) {
if (item.hasClass('unneeded') && !item.hasClass('beingdragged')) {
item.remove(true);
}
}, this);
if (this.graphics !== null) {
this.graphics.clear();
} else {
this.graphics = new Y.Graphic(
{render:this.doc.top_node().one("div.ddarea div.dropzones")}
);
}
if (this.get('dropzones').length !== 0) {
this.restart_colours();
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, true);
}
}
},
/**
* Determine what drag items need to be shown and
* return coords of all drag items except any that are currently being dragged
* based on contents of hidden inputs and whether drags are 'infinite' or how many drags should be shown.
*/
get_coords : function (input) {
var choiceno = this.get_choiceno_for_node(input);
var fv = input.get('value');
var infinite = input.hasClass('infinite');
var noofdrags = this.get_noofdrags_for_node(input);
var dragging = (null !== this.doc.drag_item_being_dragged(choiceno));
var coords = [];
if (fv !== '') {
var coordsstrings = fv.split(';');
for (var i = 0; i < coordsstrings.length; i++) {
coords[coords.length] = this.convert_to_window_xy(coordsstrings[i].split(','));
}
}
var displayeddrags = coords.length + (dragging ? 1 : 0);
if (infinite || (displayeddrags < noofdrags)) {
coords[coords.length] = this.drag_home_xy(choiceno);
}
return coords;
},
drag_home_xy : function (choiceno) {
var dragitemhome = this.doc.drag_item_home(choiceno);
return [dragitemhome.getX(), dragitemhome.getY() - 12];
},
get_choiceno_for_node : function(node) {
return Number(this.doc.get_classname_numeric_suffix(node, 'choice'));
},
get_itemno_for_node : function(node) {
return Number(this.doc.get_classname_numeric_suffix(node, 'item'));
},
get_noofdrags_for_node : function(node) {
return Number(this.doc.get_classname_numeric_suffix(node, 'noofdrags'));
},
// Keyboard accessibility stuff below here.
drop_zone_key_press : function (e) {
var dragitem = e.target;
var xy = dragitem.getXY();
switch (e.direction) {
case 'left' :
xy[0] -= 1;
break;
case 'right' :
xy[0] += 1;
break;
case 'down' :
xy[1] += 1;
break;
case 'up' :
xy[1] -= 1;
break;
case 'remove' :
xy = null;
break;
}
var choiceno = this.get_choiceno_for_node(dragitem);
if (xy !== null) {
xy = this.constrain_to_bgimg(xy);
} else {
xy = this.drag_home_xy(choiceno);
}
e.preventDefault();
dragitem.setXY(xy);
this.save_all_xy_for_choice(choiceno, null);
}
}, {NAME : DDMARKERQUESTIONNAME, ATTRS : {dropzones:{value:[]}}});
Y.Event.define('dragchange', {
// Webkit and IE repeat keydown when you hold down arrow keys.
// Opera links keypress to page scroll; others keydown.
// Firefox prevents page scroll via preventDefault() on either
// keydown or keypress.
_event: (Y.UA.webkit || Y.UA.ie) ? 'keydown' : 'keypress',
_keys: {
'32': 'remove', // Space
'37': 'left', // Left arrow
'38': 'up', // Up arrow
'39': 'right', // Right arrow
'40': 'down', // Down arrow
'65': 'left', // a
'87': 'up', // w
'68': 'right', // d
'83': 'down', // s
'27': 'remove' // Escape
},
_keyHandler: function (e, notifier) {
if (this._keys[e.keyCode]) {
e.direction = this._keys[e.keyCode];
notifier.fire(e);
}
},
on: function (node, sub, notifier) {
sub._detacher = node.on(this._event, this._keyHandler,
this, notifier);
}
});
M.qtype_ddmarker.init_question = function(config) {
return new DDMARKER_QUESTION(config);
};
}, '@VERSION@', {"requires": ["node", "event-resize", "dd", "dd-drop", "dd-constrain", "graphics"]});

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,565 @@
YUI.add('moodle-qtype_ddmarker-dd', function (Y, NAME) {
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
var DDMARKERDDNAME = 'moodle-qtype_ddmarker-dd';
var DDMARKER_DD = function() {
DDMARKER_DD.superclass.constructor.apply(this, arguments);
};
/**
* This is the base class for the question rendering and question editing form code.
*/
Y.extend(DDMARKER_DD, Y.Base, {
doc : null,
polltimer : null,
afterimageloaddone : false,
graphics : null,
poll_for_image_load : function (e, waitforimageconstrain, pause, doafterwords) {
if (this.afterimageloaddone) {
return;
}
var bgdone = this.doc.bg_img().get('complete');
if (waitforimageconstrain) {
bgdone = bgdone && this.doc.bg_img().hasClass('constrained');
}
if (bgdone) {
if (this.polltimer !== null) {
this.polltimer.cancel();
this.polltimer = null;
}
this.doc.bg_img().detach('load', this.poll_for_image_load);
if (pause !== 0) {
Y.later(pause, this, doafterwords);
} else {
doafterwords.call(this);
}
this.afterimageloaddone = true;
} else if (this.polltimer === null) {
var pollarguments = [null, waitforimageconstrain, pause, doafterwords];
this.polltimer =
Y.later(1000, this, this.poll_for_image_load, pollarguments, true);
}
},
/**
* Object to encapsulate operations on dd area.
*/
doc_structure : function () {
var topnode = Y.one(this.get('topnode'));
var dragitemsarea = topnode.one('div.dragitems');
var dropbgarea = topnode.one('div.droparea');
return {
top_node : function() {
return topnode;
},
bg_img : function() {
return topnode.one('.dropbackground');
},
load_bg_img : function (url) {
dropbgarea.setContent('<img class="dropbackground" src="' + url + '"/>');
this.bg_img().on('load', this.on_image_load, this, 'bg_image');
},
drag_items : function() {
return dragitemsarea.all('.dragitem');
},
drag_items_for_choice : function(choiceno) {
return dragitemsarea.all('span.dragitem.choice' + choiceno);
},
drag_item_for_choice : function(choiceno, itemno) {
return dragitemsarea.one('span.dragitem.choice' + choiceno +
'.item' + itemno);
},
drag_item_being_dragged : function(choiceno) {
return dragitemsarea.one('span.dragitem.beingdragged.choice' + choiceno);
},
drag_item_home : function (choiceno) {
return dragitemsarea.one('span.draghome.choice' + choiceno);
},
drag_item_homes : function() {
return dragitemsarea.all('span.draghome');
},
get_classname_numeric_suffix : function(node, prefix) {
var classes = node.getAttribute('class');
if (classes !== '') {
var classesarr = classes.split(' ');
for (var index = 0; index < classesarr.length; index++) {
var patt1 = new RegExp('^' + prefix + '([0-9])+$');
if (patt1.test(classesarr[index])) {
var patt2 = new RegExp('([0-9])+$');
var match = patt2.exec(classesarr[index]);
return Number(match[0]);
}
}
}
return null;
},
inputs_for_choices : function () {
return topnode.all('input.choices');
},
input_for_choice : function (choiceno) {
return topnode.one('input.choice' + choiceno);
},
marker_texts : function () {
return topnode.one('div.markertexts');
}
};
},
colours : ['#FFFFFF', '#B0C4DE', '#DCDCDC', '#D8BFD8',
'#87CEFA','#DAA520', '#FFD700', '#F0E68C'],
nextcolourindex : 0,
restart_colours : function () {
this.nextcolourindex = 0;
},
get_next_colour : function () {
var colour = this.colours[this.nextcolourindex];
this.nextcolourindex++;
if (this.nextcolourindex === this.colours.length) {
this.nextcolourindex = 0;
}
return colour;
},
convert_to_window_xy : function (bgimgxy) {
return [Number(bgimgxy[0]) + this.doc.bg_img().getX() + 1,
Number(bgimgxy[1]) + this.doc.bg_img().getY() + 1];
},
shapes : [],
draw_drop_zone : function (dropzoneno, markertext, shape, coords, colour, link) {
var existingmarkertext;
if (link) {
existingmarkertext = this.doc.marker_texts().one('span.markertext' + dropzoneno + ' a');
} else {
existingmarkertext = this.doc.marker_texts().one('span.markertext' + dropzoneno);
}
if (existingmarkertext) {
if (markertext !== '') {
existingmarkertext.setContent(markertext);
} else {
existingmarkertext.remove(true);
}
} else if (markertext !== '') {
var classnames = 'markertext markertext' + dropzoneno;
if (link) {
this.doc.marker_texts().append('<span class="' + classnames + '"><a href="#">' +
markertext + '</a></span>');
} else {
this.doc.marker_texts().append('<span class="' + classnames + '">' +
markertext + '</span>');
}
}
var drawfunc = 'draw_shape_' + shape;
if (this[drawfunc] instanceof Function){
var xyfortext = this[drawfunc](dropzoneno, coords, colour);
if (xyfortext !== null) {
var markerspan = this.doc.top_node().one('div.ddarea div.markertexts span.markertext' + dropzoneno);
if (markerspan !== null) {
markerspan.setStyle('opacity', '0.6');
xyfortext[0] -= markerspan.get('offsetWidth') / 2;
xyfortext[1] -= markerspan.get('offsetHeight') / 2;
markerspan.setXY(this.convert_to_window_xy(xyfortext));
var markerspananchor = markerspan.one('a');
if (markerspananchor !== null) {
markerspananchor.once('click', function (e, dropzoneno) {
var fill = this.shapes[dropzoneno].get('fill');
fill.opacity = 1;
this.shapes[dropzoneno].set('fill', fill);
},
this,
dropzoneno
);
markerspananchor.set('tabIndex', 0);
}
}
}
}
},
draw_shape_circle : function (dropzoneno, coords, colour) {
var coordsparts = coords.match(/(\d+),(\d+);(\d+)/);
if (coordsparts && coordsparts.length === 4) {
var xy = [Number(coordsparts[1]) - coordsparts[3], Number(coordsparts[2]) - coordsparts[3]];
if (this.coords_in_img(xy)) {
var widthheight = [Number(coordsparts[3]) * 2, Number(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 [Number(coordsparts[1]), Number(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 = [Number(coordsparts[1]), Number(coordsparts[2])];
var widthheight = [Number(coordsparts[3]), Number(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 [Number(xy[0]) + widthheight[0] / 2, Number(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 (i = 0; i < xy.length; i++) {
//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 (Number(xy[0][0]) !== Number(xy[xy.length - 1][0]) || Number(xy[0][1]) !== Number(xy[xy.length - 1][1])) {
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 [(minxy[0] + maxxy[0]) / 2, (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,
ATTRS : {
drops : {value : null},
readonly : {value : false},
topnode : {value : null}
}
});
M.qtype_ddmarker = M.qtype_ddmarker || {};
M.qtype_ddmarker.dd_base_class = DDMARKER_DD;
var DDMARKERQUESTIONNAME = 'ddmarker_question';
var DDMARKER_QUESTION = function() {
DDMARKER_QUESTION.superclass.constructor.apply(this, arguments);
};
/**
* This is the code for question rendering.
*/
Y.extend(DDMARKER_QUESTION, M.qtype_ddmarker.dd_base_class, {
initializer : function() {
this.doc = this.doc_structure(this);
this.poll_for_image_load(null, false, 0, this.after_image_load);
this.doc.bg_img().after('load', this.poll_for_image_load, this,
false, 0, this.after_image_load);
},
after_image_load : function () {
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);
drag.removeClass('draghome');
drag.addClass('dragitem');
drag.addClass('item' + itemno);
drag.one('span.markertext').setStyle('opacity', 0.6);
draghome.insert(drag, 'after');
if (!this.get('readonly')) {
this.draggable(drag);
}
return drag;
},
draggable : function (drag) {
var dd = new Y.DD.Drag({
node: drag,
dragMode: 'intersect'
}).plug(Y.Plugin.DDConstrained, {constrain2node: this.doc.top_node()});
dd.after('drag:start', function(e){
var dragnode = e.target.get('node');
dragnode.addClass('beingdragged');
var choiceno = this.get_choiceno_for_node(dragnode);
var itemno = this.get_itemno_for_node(dragnode);
if (itemno !== null) {
dragnode.removeClass('item' + dragnode);
}
this.save_all_xy_for_choice(choiceno, null);
this.redraw_drags_and_drops();
}, this);
dd.after('drag:end', function(e) {
var dragnode = e.target.get('node');
dragnode.removeClass('beingdragged');
var choiceno = this.get_choiceno_for_node(dragnode);
this.save_all_xy_for_choice(choiceno, dragnode);
this.redraw_drags_and_drops();
}, this);
//--- keyboard accessibility
drag.set('tabIndex', 0);
drag.on('dragchange', this.drop_zone_key_press, this);
},
save_all_xy_for_choice: function (choiceno, dropped) {
var coords = [];
var bgimgxy;
for (var i = 0; i <= this.doc.drag_items_for_choice(choiceno).size(); i++) {
var dragitem = this.doc.drag_item_for_choice(choiceno, i);
if (dragitem) {
dragitem.removeClass('item' + i);
if (!dragitem.hasClass('beingdragged')) {
bgimgxy = this.convert_to_bg_img_xy(dragitem.getXY());
if (this.xy_in_bgimg(bgimgxy)) {
dragitem.removeClass('item' + i);
dragitem.addClass('item' + coords.length);
coords[coords.length] = bgimgxy;
}
}
}
}
if (dropped !== null){
bgimgxy = this.convert_to_bg_img_xy(dropped.getXY());
dropped.addClass('item' + coords.length);
if (this.xy_in_bgimg(bgimgxy)) {
coords[coords.length] = bgimgxy;
}
}
this.set_form_value(choiceno, coords.join(';'));
},
reset_drag_xy : function (choiceno) {
this.set_form_value(choiceno, '');
},
set_form_value : function (choiceno, value) {
this.doc.input_for_choice(choiceno).set('value', value);
},
//make sure xy value is not out of bounds of bg image
xy_in_bgimg : function (bgimgxy) {
if ((bgimgxy[0] < 0) ||
(bgimgxy[1] < 0) ||
(bgimgxy[0] > this.doc.bg_img().get('width')) ||
(bgimgxy[1] > this.doc.bg_img().get('height'))){
return false;
} else {
return true;
}
},
constrain_to_bgimg : function (windowxy) {
var bgimgxy = this.convert_to_bg_img_xy(windowxy);
bgimgxy[0] = Math.max(0, bgimgxy[0]);
bgimgxy[1] = Math.max(0, bgimgxy[1]);
bgimgxy[0] = Math.min(this.doc.bg_img().get('width'), bgimgxy[0]);
bgimgxy[1] = Math.min(this.doc.bg_img().get('height'), bgimgxy[1]);
return this.convert_to_window_xy(bgimgxy);
},
convert_to_bg_img_xy : function (windowxy) {
return [Number(windowxy[0]) - this.doc.bg_img().getX() - 1,
Number(windowxy[1]) - this.doc.bg_img().getY() - 1];
},
redraw_drags_and_drops : function() {
this.doc.drag_items().each(function(item) {
//if (!item.hasClass('beingdragged')){
item.addClass('unneeded');
//}
}, this);
this.doc.inputs_for_choices().each(function (input) {
var choiceno = this.get_choiceno_for_node(input);
var coords = this.get_coords(input);
var dragitemhome = this.doc.drag_item_home(choiceno);
for (var i = 0; i < coords.length; i++) {
var dragitem = this.doc.drag_item_for_choice(choiceno, i);
if (!dragitem || dragitem.hasClass('beingdragged')) {
dragitem = this.clone_new_drag_item(dragitemhome, i);
} else {
dragitem.removeClass('unneeded');
}
dragitem.setXY(coords[i]);
}
}, this);
this.doc.drag_items().each(function(item) {
if (item.hasClass('unneeded') && !item.hasClass('beingdragged')) {
item.remove(true);
}
}, this);
if (this.graphics !== null) {
this.graphics.clear();
} else {
this.graphics = new Y.Graphic(
{render:this.doc.top_node().one("div.ddarea div.dropzones")}
);
}
if (this.get('dropzones').length !== 0) {
this.restart_colours();
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, true);
}
}
},
/**
* Determine what drag items need to be shown and
* return coords of all drag items except any that are currently being dragged
* based on contents of hidden inputs and whether drags are 'infinite' or how many drags should be shown.
*/
get_coords : function (input) {
var choiceno = this.get_choiceno_for_node(input);
var fv = input.get('value');
var infinite = input.hasClass('infinite');
var noofdrags = this.get_noofdrags_for_node(input);
var dragging = (null !== this.doc.drag_item_being_dragged(choiceno));
var coords = [];
if (fv !== '') {
var coordsstrings = fv.split(';');
for (var i = 0; i < coordsstrings.length; i++) {
coords[coords.length] = this.convert_to_window_xy(coordsstrings[i].split(','));
}
}
var displayeddrags = coords.length + (dragging ? 1 : 0);
if (infinite || (displayeddrags < noofdrags)) {
coords[coords.length] = this.drag_home_xy(choiceno);
}
return coords;
},
drag_home_xy : function (choiceno) {
var dragitemhome = this.doc.drag_item_home(choiceno);
return [dragitemhome.getX(), dragitemhome.getY() - 12];
},
get_choiceno_for_node : function(node) {
return Number(this.doc.get_classname_numeric_suffix(node, 'choice'));
},
get_itemno_for_node : function(node) {
return Number(this.doc.get_classname_numeric_suffix(node, 'item'));
},
get_noofdrags_for_node : function(node) {
return Number(this.doc.get_classname_numeric_suffix(node, 'noofdrags'));
},
// Keyboard accessibility stuff below here.
drop_zone_key_press : function (e) {
var dragitem = e.target;
var xy = dragitem.getXY();
switch (e.direction) {
case 'left' :
xy[0] -= 1;
break;
case 'right' :
xy[0] += 1;
break;
case 'down' :
xy[1] += 1;
break;
case 'up' :
xy[1] -= 1;
break;
case 'remove' :
xy = null;
break;
}
var choiceno = this.get_choiceno_for_node(dragitem);
if (xy !== null) {
xy = this.constrain_to_bgimg(xy);
} else {
xy = this.drag_home_xy(choiceno);
}
e.preventDefault();
dragitem.setXY(xy);
this.save_all_xy_for_choice(choiceno, null);
}
}, {NAME : DDMARKERQUESTIONNAME, ATTRS : {dropzones:{value:[]}}});
Y.Event.define('dragchange', {
// Webkit and IE repeat keydown when you hold down arrow keys.
// Opera links keypress to page scroll; others keydown.
// Firefox prevents page scroll via preventDefault() on either
// keydown or keypress.
_event: (Y.UA.webkit || Y.UA.ie) ? 'keydown' : 'keypress',
_keys: {
'32': 'remove', // Space
'37': 'left', // Left arrow
'38': 'up', // Up arrow
'39': 'right', // Right arrow
'40': 'down', // Down arrow
'65': 'left', // a
'87': 'up', // w
'68': 'right', // d
'83': 'down', // s
'27': 'remove' // Escape
},
_keyHandler: function (e, notifier) {
if (this._keys[e.keyCode]) {
e.direction = this._keys[e.keyCode];
notifier.fire(e);
}
},
on: function (node, sub, notifier) {
sub._detacher = node.on(this._event, this._keyHandler,
this, notifier);
}
});
M.qtype_ddmarker.init_question = function(config) {
return new DDMARKER_QUESTION(config);
};
}, '@VERSION@', {"requires": ["node", "event-resize", "dd", "dd-drop", "dd-constrain", "graphics"]});

View file

@ -0,0 +1,272 @@
YUI.add('moodle-qtype_ddmarker-form', function (Y, NAME) {
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This is the question editing form code.
*/
var DDMARKERFORMNAME = 'moodle-qtype_ddmarker-form';
var DDMARKER_FORM = function() {
DDMARKER_FORM.superclass.constructor.apply(this, arguments);
};
Y.extend(DDMARKER_FORM, M.qtype_ddmarker.dd_base_class, {
fp : null,
initializer : function() {
this.fp = this.file_pickers();
var tn = Y.one(this.get('topnode'));
tn.one('div.fcontainer').append(
'<div class="ddarea">' +
'<div class="markertexts"></div>' +
'<div class="droparea"></div>' +
'<div class="dropzones"></div>' +
'<div class="grid"></div>' +
'</div>');
this.doc = this.doc_structure(this);
this.stop_selector_events();
this.set_options_for_drag_item_selectors();
this.setup_form_events();
Y.later(500, this, this.update_drop_zones, [], true);
Y.after(this.load_bg_image, M.form_filepicker, 'callback', this);
this.load_bg_image();
},
load_bg_image : function() {
var bgimageurl = this.fp.file('bgimage').href;
if (bgimageurl !== null) {
this.doc.load_bg_img(bgimageurl);
var drop = new Y.DD.Drop({
node: this.doc.bg_img()
});
// Listen for a drop:hit on the background image.
drop.on('drop:hit', function(e) {
e.drag.get('node').setData('gooddrop', true);
});
this.afterimageloaddone = false;
this.doc.bg_img().on('load', this.constrain_image_size, this);
}
},
constrain_image_size : function (e) {
var maxsize = this.get('maxsizes').bgimage;
var reduceby = Math.max(e.target.get('width') / maxsize.width,
e.target.get('height') / maxsize.height);
if (reduceby > 1) {
e.target.set('width', Math.floor(e.target.get('width') / reduceby));
}
e.target.addClass('constrained');
e.target.detach('load', this.constrain_image_size);
},
update_drop_zones : function () {
// Set up drop zones.
if (this.graphics !== null) {
this.graphics.destroy();
}
this.restart_colours();
this.graphics = new Y.Graphic({render:"div.ddarea div.dropzones"});
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);
this.draw_drop_zone(dropzoneno, markertext,
shape, coords, colourfordropzone, false);
}
Y.one('div.ddarea .grid')
.setXY(this.doc.bg_img().getXY())
.setStyle('width', this.doc.bg_img().get('width'))
.setStyle('height', 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'), '');
},
get_marker_text : function (markerno) {
if (Number(markerno) !== 0) {
var label = this.form.get_form_value('drags', [markerno - 1, 'label']);
return label.replace(new RegExp("^\\s*(.*)\\s*$"), "$1");
} else {
return '';
}
},
set_options_for_drag_item_selectors : function () {
var dragitemsoptions = {0: ''};
for (var i = 1; i <= this.form.get_form_value('noitems', []); i++) {
var label = this.get_marker_text(i);
if (label !== "") {
dragitemsoptions[i] = Y.Escape.html(label);
}
}
// Get all the currently selected drags for each drop.
var selectedvalues = [];
var selector;
for (i = 0; i < this.form.get_form_value('nodropzone', []); i++) {
selector = Y.one('#id_drops_' + i + '_choice');
selectedvalues[i] = Number(selector.get('value'));
}
for (i = 0; i < this.form.get_form_value('nodropzone', []); i++) {
selector = Y.one('#id_drops_' + i + '_choice');
// Remove all options for drag choice.
selector.all('option').remove(true);
// And recreate the options.
for (var value in dragitemsoptions) {
value = Number(value);
var option = '<option value="' + value + '">' + dragitemsoptions[value] + '</option>';
selector.append(option);
var optionnode = selector.one('option[value="' + value + '"]');
// Is this the currently selected value?
if (value === selectedvalues[i]) {
optionnode.set('selected', true);
} else {
// It is not the currently selected value, is it selectable?
if (value !== 0) { // The 'no item' option is always selectable.
// Variables to hold form values about this drag item.
var noofdrags = this.form.get_form_value('drags', [value - 1, 'noofdrags']);
if (Number(noofdrags) !== 0) { // 'noofdrags == 0' means infinite.
// Go through all selected values in drop downs.
for (var k in selectedvalues) {
// Count down 'noofdrags' and if reach zero then set disabled option for this drag item.
if (Number(selectedvalues[k]) === value) {
if (Number(noofdrags) === 1) {
optionnode.set('disabled', true);
break;
} else {
noofdrags--;
}
}
}
}
}
}
}
}
},
stop_selector_events : function () {
Y.all('fieldset#id_dropzoneheader select').detachAll();
},
setup_form_events : function () {
//events triggered by changes to form data
// Changes to labels.
Y.all('fieldset#id_draggableitemheader input').on('change', function () {
this.set_options_for_drag_item_selectors();
}, this);
// Changes to selected drag item.
Y.all('fieldset#id_draggableitemheader select').on('change', function () {
this.set_options_for_drag_item_selectors();
}, this);
// Change in selected item.
Y.all('fieldset#id_dropzoneheader select').on('change', function () {
this.set_options_for_drag_item_selectors();
}, this);
},
/**
* Low level operations on form.
*/
form : {
to_name_with_index : function(name, indexes) {
var indexstring = name;
for (var i = 0; i < indexes.length; i++) {
indexstring = indexstring + '[' + indexes[i] + ']';
}
return indexstring;
},
get_el : function (name, indexes) {
var form = document.getElementById('mform1');
return form.elements[this.to_name_with_index(name, indexes)];
},
get_form_value : function(name, indexes) {
var el = this.get_el(name, indexes);
if (el.type === 'checkbox') {
return el.checked;
} else {
return el.value;
}
},
set_form_value : function(name, indexes, value) {
var el = this.get_el(name, indexes);
if (el.type === 'checkbox') {
el.checked = value;
} else {
el.value = value;
}
},
from_name_with_index : function(name) {
var toreturn = {};
toreturn.indexes = [];
var bracket = name.indexOf('[');
toreturn.name = name.substring(0, bracket);
while (bracket !== -1) {
var end = name.indexOf(']', bracket + 1);
toreturn.indexes.push(name.substring(bracket + 1, end));
bracket = name.indexOf('[', end + 1);
}
return toreturn;
}
},
file_pickers : function () {
var draftitemidstoname;
var nametoparentnode;
if (draftitemidstoname === undefined) {
draftitemidstoname = {};
nametoparentnode = {};
var filepickers = Y.all('form.mform input.filepickerhidden');
filepickers.each(function(filepicker) {
draftitemidstoname[filepicker.get('value')] = filepicker.get('name');
nametoparentnode[filepicker.get('name')] = filepicker.get('parentNode');
}, this);
}
var toreturn = {
file : function (name) {
var parentnode = nametoparentnode[name];
var fileanchor = parentnode.one('div.filepicker-filelist a');
if (fileanchor) {
return {href : fileanchor.get('href'), name : fileanchor.get('innerHTML')};
} else {
return {href : null, name : null};
}
},
name : function (draftitemid) {
return draftitemidstoname[draftitemid];
}
};
return toreturn;
}
},{NAME : DDMARKERFORMNAME, ATTRS : {maxsizes:{value:null}}});
M.qtype_ddmarker = M.qtype_ddmarker || {};
M.qtype_ddmarker.init_form = function(config) {
return new DDMARKER_FORM(config);
};
}, '@VERSION@', {"requires": ["moodle-qtype_ddmarker-dd", "form_filepicker", "graphics", "escape"]});

View file

@ -0,0 +1 @@
YUI.add("moodle-qtype_ddmarker-form",function(e,t){var n="moodle-qtype_ddmarker-form",r=function(){r.superclass.constructor.apply(this,arguments)};e.extend(r,M.qtype_ddmarker.dd_base_class,{fp:null,initializer:function(){this.fp=this.file_pickers();var t=e.one(this.get("topnode"));t.one("div.fcontainer").append('<div class="ddarea"><div class="markertexts"></div><div class="droparea"></div><div class="dropzones"></div><div class="grid"></div></div>'),this.doc=this.doc_structure(this),this.stop_selector_events(),this.set_options_for_drag_item_selectors(),this.setup_form_events(),e.later(500,this,this.update_drop_zones,[],!0),e.after(this.load_bg_image,M.form_filepicker,"callback",this),this.load_bg_image()},load_bg_image:function(){var t=this.fp.file("bgimage").href;if(t!==null){this.doc.load_bg_img(t);var n=new e.DD.Drop({node:this.doc.bg_img()});n.on("drop:hit",function(e){e.drag.get("node").setData("gooddrop",!0)}),this.afterimageloaddone=!1,this.doc.bg_img().on("load",this.constrain_image_size,this)}},constrain_image_size:function(e){var t=this.get("maxsizes").bgimage,n=Math.max(e.target.get("width")/t.width,e.target.get("height")/t.height);n>1&&e.target.set("width",Math.floor(e.target.get("width")/n)),e.target.addClass("constrained"),e.target.detach("load",this.constrain_image_size)},update_drop_zones:function(){this.graphics!==null&&this.graphics.destroy(),this.restart_colours(),this.graphics=new e.Graphic({render:"div.ddarea div.dropzones"});var t=this.form.get_form_value("nodropzone",[]);for(var n=0;n<t;n++){var r=this.form.get_form_value("drops",[n,"choice"]),i=this.get_marker_text(r),s=this.form.get_form_value("drops",[n,"shape"]),o=this.get_coords(n),u=this.get_next_colour();e.one("input#id_drops_"+n+"_coords").setStyle("background-color",u),this.draw_drop_zone(n,i,s,o,u,!1)}e.one("div.ddarea .grid").setXY(this.doc.bg_img().getXY()).setStyle("width",this.doc.bg_img().get("width")).setStyle("height",this.doc.bg_img().get("height"))},get_coords:function(e){var t=this.form.get_form_value("drops",[e,"coords"]);return t.replace(new RegExp("\\s*","g"),"")},get_marker_text:function(e){if(Number(e)!==0){var t=this.form.get_form_value("drags",[e-1,"label"]);return t.replace(new RegExp("^\\s*(.*)\\s*$"),"$1")}return""},set_options_for_drag_item_selectors:function(){var t={0:""};for(var n=1;n<=this.form.get_form_value("noitems",[]);n++){var r=this.get_marker_text(n);r!==""&&(t[n]=e.Escape.html(r))}var i=[],s;for(n=0;n<this.form.get_form_value("nodropzone",[]);n++)s=e.one("#id_drops_"+n+"_choice"),i[n]=Number(s.get("value"));for(n=0;n<this.form.get_form_value("nodropzone",[]);n++){s=e.one("#id_drops_"+n+"_choice"),s.all("option").remove(!0);for(var o in t){o=Number(o);var u='<option value="'+o+'">'+t[o]+"</option>";s.append(u);var a=s.one('option[value="'+o+'"]');if(o===i[n])a.set("selected",!0);else if(o!==0){var f=this.form.get_form_value("drags",[o-1,"noofdrags"]);if(Number(f)!==0)for(var l in i)if(Number(i[l])===o){if(Number(f)===1){a.set("disabled",!0);break}f--}}}}},stop_selector_events:function(){e.all("fieldset#id_dropzoneheader select").detachAll()},setup_form_events:function(){e.all("fieldset#id_draggableitemheader input").on("change",function(){this.set_options_for_drag_item_selectors()},this),e.all("fieldset#id_draggableitemheader select").on("change",function(){this.set_options_for_drag_item_selectors()},this),e.all("fieldset#id_dropzoneheader select").on("change",function(){this.set_options_for_drag_item_selectors()},this)},form:{to_name_with_index:function(e,t){var n=e;for(var r=0;r<t.length;r++)n=n+"["+t[r]+"]";return n},get_el:function(e,t){var n=document.getElementById("mform1");return n.elements[this.to_name_with_index(e,t)]},get_form_value:function(e,t){var n=this.get_el(e,t);return n.type==="checkbox"?n.checked:n.value},set_form_value:function(e,t,n){var r=this.get_el(e,t);r.type==="checkbox"?r.checked=n:r.value=n},from_name_with_index:function(e){var t={};t.indexes=[];var n=e.indexOf("[");t.name=e.substring(0,n);while(n!==-1){var r=e.indexOf("]",n+1);t.indexes.push(e.substring(n+1,r)),n=e.indexOf("[",r+1)}return t}},file_pickers:function(){var t,n;if(t===undefined){t={},n={};var r=e.all("form.mform input.filepickerhidden");r.each(function(e){t[e.get("value")]=e.get("name"),n[e.get("name")]=e.get("parentNode")},this)}var i={file:function(e){var t=n[e],r=t.one("div.filepicker-filelist a");return r?{href:r.get("href"),name:r.get("innerHTML")}:{href:null,name:null}},name:function(e){return t[e]}};return i}},{NAME:n,ATTRS:{maxsizes:{value:null}}}),M.qtype_ddmarker=M.qtype_ddmarker||{},M.qtype_ddmarker.init_form=function(e){return new r(e)}},"@VERSION@",{requires:["moodle-qtype_ddmarker-dd","form_filepicker","graphics","escape"]});

View file

@ -0,0 +1,272 @@
YUI.add('moodle-qtype_ddmarker-form', function (Y, NAME) {
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This is the question editing form code.
*/
var DDMARKERFORMNAME = 'moodle-qtype_ddmarker-form';
var DDMARKER_FORM = function() {
DDMARKER_FORM.superclass.constructor.apply(this, arguments);
};
Y.extend(DDMARKER_FORM, M.qtype_ddmarker.dd_base_class, {
fp : null,
initializer : function() {
this.fp = this.file_pickers();
var tn = Y.one(this.get('topnode'));
tn.one('div.fcontainer').append(
'<div class="ddarea">' +
'<div class="markertexts"></div>' +
'<div class="droparea"></div>' +
'<div class="dropzones"></div>' +
'<div class="grid"></div>' +
'</div>');
this.doc = this.doc_structure(this);
this.stop_selector_events();
this.set_options_for_drag_item_selectors();
this.setup_form_events();
Y.later(500, this, this.update_drop_zones, [], true);
Y.after(this.load_bg_image, M.form_filepicker, 'callback', this);
this.load_bg_image();
},
load_bg_image : function() {
var bgimageurl = this.fp.file('bgimage').href;
if (bgimageurl !== null) {
this.doc.load_bg_img(bgimageurl);
var drop = new Y.DD.Drop({
node: this.doc.bg_img()
});
// Listen for a drop:hit on the background image.
drop.on('drop:hit', function(e) {
e.drag.get('node').setData('gooddrop', true);
});
this.afterimageloaddone = false;
this.doc.bg_img().on('load', this.constrain_image_size, this);
}
},
constrain_image_size : function (e) {
var maxsize = this.get('maxsizes').bgimage;
var reduceby = Math.max(e.target.get('width') / maxsize.width,
e.target.get('height') / maxsize.height);
if (reduceby > 1) {
e.target.set('width', Math.floor(e.target.get('width') / reduceby));
}
e.target.addClass('constrained');
e.target.detach('load', this.constrain_image_size);
},
update_drop_zones : function () {
// Set up drop zones.
if (this.graphics !== null) {
this.graphics.destroy();
}
this.restart_colours();
this.graphics = new Y.Graphic({render:"div.ddarea div.dropzones"});
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);
this.draw_drop_zone(dropzoneno, markertext,
shape, coords, colourfordropzone, false);
}
Y.one('div.ddarea .grid')
.setXY(this.doc.bg_img().getXY())
.setStyle('width', this.doc.bg_img().get('width'))
.setStyle('height', 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'), '');
},
get_marker_text : function (markerno) {
if (Number(markerno) !== 0) {
var label = this.form.get_form_value('drags', [markerno - 1, 'label']);
return label.replace(new RegExp("^\\s*(.*)\\s*$"), "$1");
} else {
return '';
}
},
set_options_for_drag_item_selectors : function () {
var dragitemsoptions = {0: ''};
for (var i = 1; i <= this.form.get_form_value('noitems', []); i++) {
var label = this.get_marker_text(i);
if (label !== "") {
dragitemsoptions[i] = Y.Escape.html(label);
}
}
// Get all the currently selected drags for each drop.
var selectedvalues = [];
var selector;
for (i = 0; i < this.form.get_form_value('nodropzone', []); i++) {
selector = Y.one('#id_drops_' + i + '_choice');
selectedvalues[i] = Number(selector.get('value'));
}
for (i = 0; i < this.form.get_form_value('nodropzone', []); i++) {
selector = Y.one('#id_drops_' + i + '_choice');
// Remove all options for drag choice.
selector.all('option').remove(true);
// And recreate the options.
for (var value in dragitemsoptions) {
value = Number(value);
var option = '<option value="' + value + '">' + dragitemsoptions[value] + '</option>';
selector.append(option);
var optionnode = selector.one('option[value="' + value + '"]');
// Is this the currently selected value?
if (value === selectedvalues[i]) {
optionnode.set('selected', true);
} else {
// It is not the currently selected value, is it selectable?
if (value !== 0) { // The 'no item' option is always selectable.
// Variables to hold form values about this drag item.
var noofdrags = this.form.get_form_value('drags', [value - 1, 'noofdrags']);
if (Number(noofdrags) !== 0) { // 'noofdrags == 0' means infinite.
// Go through all selected values in drop downs.
for (var k in selectedvalues) {
// Count down 'noofdrags' and if reach zero then set disabled option for this drag item.
if (Number(selectedvalues[k]) === value) {
if (Number(noofdrags) === 1) {
optionnode.set('disabled', true);
break;
} else {
noofdrags--;
}
}
}
}
}
}
}
}
},
stop_selector_events : function () {
Y.all('fieldset#id_dropzoneheader select').detachAll();
},
setup_form_events : function () {
//events triggered by changes to form data
// Changes to labels.
Y.all('fieldset#id_draggableitemheader input').on('change', function () {
this.set_options_for_drag_item_selectors();
}, this);
// Changes to selected drag item.
Y.all('fieldset#id_draggableitemheader select').on('change', function () {
this.set_options_for_drag_item_selectors();
}, this);
// Change in selected item.
Y.all('fieldset#id_dropzoneheader select').on('change', function () {
this.set_options_for_drag_item_selectors();
}, this);
},
/**
* Low level operations on form.
*/
form : {
to_name_with_index : function(name, indexes) {
var indexstring = name;
for (var i = 0; i < indexes.length; i++) {
indexstring = indexstring + '[' + indexes[i] + ']';
}
return indexstring;
},
get_el : function (name, indexes) {
var form = document.getElementById('mform1');
return form.elements[this.to_name_with_index(name, indexes)];
},
get_form_value : function(name, indexes) {
var el = this.get_el(name, indexes);
if (el.type === 'checkbox') {
return el.checked;
} else {
return el.value;
}
},
set_form_value : function(name, indexes, value) {
var el = this.get_el(name, indexes);
if (el.type === 'checkbox') {
el.checked = value;
} else {
el.value = value;
}
},
from_name_with_index : function(name) {
var toreturn = {};
toreturn.indexes = [];
var bracket = name.indexOf('[');
toreturn.name = name.substring(0, bracket);
while (bracket !== -1) {
var end = name.indexOf(']', bracket + 1);
toreturn.indexes.push(name.substring(bracket + 1, end));
bracket = name.indexOf('[', end + 1);
}
return toreturn;
}
},
file_pickers : function () {
var draftitemidstoname;
var nametoparentnode;
if (draftitemidstoname === undefined) {
draftitemidstoname = {};
nametoparentnode = {};
var filepickers = Y.all('form.mform input.filepickerhidden');
filepickers.each(function(filepicker) {
draftitemidstoname[filepicker.get('value')] = filepicker.get('name');
nametoparentnode[filepicker.get('name')] = filepicker.get('parentNode');
}, this);
}
var toreturn = {
file : function (name) {
var parentnode = nametoparentnode[name];
var fileanchor = parentnode.one('div.filepicker-filelist a');
if (fileanchor) {
return {href : fileanchor.get('href'), name : fileanchor.get('innerHTML')};
} else {
return {href : null, name : null};
}
},
name : function (draftitemid) {
return draftitemidstoname[draftitemid];
}
};
return toreturn;
}
},{NAME : DDMARKERFORMNAME, ATTRS : {maxsizes:{value:null}}});
M.qtype_ddmarker = M.qtype_ddmarker || {};
M.qtype_ddmarker.init_form = function(config) {
return new DDMARKER_FORM(config);
};
}, '@VERSION@', {"requires": ["moodle-qtype_ddmarker-dd", "form_filepicker", "graphics", "escape"]});

View file

@ -1,549 +0,0 @@
YUI.add('moodle-qtype_ddmarker-dd', function(Y) {
var DDMARKERDDNAME = 'ddmarker_dd';
var DDMARKER_DD = function() {
DDMARKER_DD.superclass.constructor.apply(this, arguments);
};
/**
* This is the base class for the question rendering and question editing form code.
*/
Y.extend(DDMARKER_DD, Y.Base, {
doc : null,
polltimer : null,
afterimageloaddone : false,
graphics : null,
poll_for_image_load : function (e, waitforimageconstrain, pause, doafterwords) {
if (this.afterimageloaddone) {
return;
}
var bgdone = this.doc.bg_img().get('complete');
if (waitforimageconstrain) {
bgdone = bgdone && this.doc.bg_img().hasClass('constrained');
}
if (bgdone) {
if (this.polltimer !== null) {
this.polltimer.cancel();
this.polltimer = null;
}
this.doc.bg_img().detach('load', this.poll_for_image_load);
if (pause !== 0) {
Y.later(pause, this, doafterwords);
} else {
doafterwords.call(this);
}
this.afterimageloaddone = true;
} else if (this.polltimer === null) {
var pollarguments = [null, waitforimageconstrain, pause, doafterwords];
this.polltimer =
Y.later(1000, this, this.poll_for_image_load, pollarguments, true);
}
},
/**
* Object to encapsulate operations on dd area.
*/
doc_structure : function () {
var topnode = Y.one(this.get('topnode'));
var dragitemsarea = topnode.one('div.dragitems');
var dropbgarea = topnode.one('div.droparea');
return {
top_node : function() {
return topnode;
},
bg_img : function() {
return topnode.one('.dropbackground');
},
load_bg_img : function (url) {
dropbgarea.setContent('<img class="dropbackground" src="' + url + '"/>');
this.bg_img().on('load', this.on_image_load, this, 'bg_image');
},
drag_items : function() {
return dragitemsarea.all('.dragitem');
},
drag_items_for_choice : function(choiceno) {
return dragitemsarea.all('span.dragitem.choice' + choiceno);
},
drag_item_for_choice : function(choiceno, itemno) {
return dragitemsarea.one('span.dragitem.choice' + choiceno +
'.item' + itemno);
},
drag_item_being_dragged : function(choiceno) {
return dragitemsarea.one('span.dragitem.beingdragged.choice' + choiceno);
},
drag_item_home : function (choiceno) {
return dragitemsarea.one('span.draghome.choice' + choiceno);
},
drag_item_homes : function() {
return dragitemsarea.all('span.draghome');
},
get_classname_numeric_suffix : function(node, prefix) {
var classes = node.getAttribute('class');
if (classes !== '') {
var classesarr = classes.split(' ');
for (var index = 0; index < classesarr.length; index++) {
var patt1 = new RegExp('^' + prefix + '([0-9])+$');
if (patt1.test(classesarr[index])) {
var patt2 = new RegExp('([0-9])+$');
var match = patt2.exec(classesarr[index]);
return Number(match[0]);
}
}
}
return null;
},
inputs_for_choices : function () {
return topnode.all('input.choices');
},
input_for_choice : function (choiceno) {
return topnode.one('input.choice' + choiceno);
},
marker_texts : function () {
return topnode.one('div.markertexts');
}
};
},
colours : ['#FFFFFF', '#B0C4DE', '#DCDCDC', '#D8BFD8',
'#87CEFA','#DAA520', '#FFD700', '#F0E68C'],
nextcolourindex : 0,
restart_colours : function () {
this.nextcolourindex = 0;
},
get_next_colour : function () {
var colour = this.colours[this.nextcolourindex];
this.nextcolourindex++;
if (this.nextcolourindex === this.colours.length) {
this.nextcolourindex = 0;
}
return colour;
},
convert_to_window_xy : function (bgimgxy) {
return [Number(bgimgxy[0]) + this.doc.bg_img().getX() + 1,
Number(bgimgxy[1]) + this.doc.bg_img().getY() + 1];
},
shapes : [],
draw_drop_zone : function (dropzoneno, markertext, shape, coords, colour, link) {
var existingmarkertext;
if (link) {
existingmarkertext = this.doc.marker_texts().one('span.markertext' + dropzoneno + ' a');
} else {
existingmarkertext = this.doc.marker_texts().one('span.markertext' + dropzoneno);
}
if (existingmarkertext) {
if (markertext !== '') {
existingmarkertext.setContent(markertext);
} else {
existingmarkertext.remove(true);
}
} else if (markertext !== '') {
var classnames = 'markertext markertext' + dropzoneno;
if (link) {
this.doc.marker_texts().append('<span class="' + classnames + '"><a href="#">' +
markertext + '</a></span>');
} else {
this.doc.marker_texts().append('<span class="' + classnames + '">' +
markertext + '</span>');
}
}
var drawfunc = 'draw_shape_' + shape;
if (this[drawfunc] instanceof Function){
var xyfortext = this[drawfunc](dropzoneno, coords, colour);
if (xyfortext !== null) {
var markerspan = this.doc.top_node().one('div.ddarea div.markertexts span.markertext' + dropzoneno);
if (markerspan !== null) {
markerspan.setStyle('opacity', '0.6');
xyfortext[0] -= markerspan.get('offsetWidth') / 2;
xyfortext[1] -= markerspan.get('offsetHeight') / 2;
markerspan.setXY(this.convert_to_window_xy(xyfortext));
var markerspananchor = markerspan.one('a');
if (markerspananchor !== null) {
markerspananchor.once('click', function (e, dropzoneno) {
var fill = this.shapes[dropzoneno].get('fill');
fill.opacity = 1;
this.shapes[dropzoneno].set('fill', fill);
},
this,
dropzoneno
);
markerspananchor.set('tabIndex', 0);
}
}
}
}
},
draw_shape_circle : function (dropzoneno, coords, colour) {
var coordsparts = coords.match(/(\d+),(\d+);(\d+)/);
if (coordsparts && coordsparts.length === 4) {
var xy = [Number(coordsparts[1]) - coordsparts[3], Number(coordsparts[2]) - coordsparts[3]];
if (this.coords_in_img(xy)) {
var widthheight = [Number(coordsparts[3]) * 2, Number(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 [Number(coordsparts[1]), Number(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 = [Number(coordsparts[1]), Number(coordsparts[2])];
var widthheight = [Number(coordsparts[3]), Number(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 [Number(xy[0]) + widthheight[0] / 2, Number(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 (i = 0; i < xy.length; i++) {
//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 (Number(xy[0][0]) !== Number(xy[xy.length - 1][0]) || Number(xy[0][1]) !== Number(xy[xy.length - 1][1])) {
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 [(minxy[0] + maxxy[0]) / 2, (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,
ATTRS : {
drops : {value : null},
readonly : {value : false},
topnode : {value : null}
}
});
M.qtype_ddmarker = M.qtype_ddmarker || {};
M.qtype_ddmarker.dd_base_class = DDMARKER_DD;
var DDMARKERQUESTIONNAME = 'ddmarker_question';
var DDMARKER_QUESTION = function() {
DDMARKER_QUESTION.superclass.constructor.apply(this, arguments);
};
/**
* This is the code for question rendering.
*/
Y.extend(DDMARKER_QUESTION, M.qtype_ddmarker.dd_base_class, {
initializer : function() {
this.doc = this.doc_structure(this);
this.poll_for_image_load(null, false, 0, this.after_image_load);
this.doc.bg_img().after('load', this.poll_for_image_load, this,
false, 0, this.after_image_load);
},
after_image_load : function () {
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);
drag.removeClass('draghome');
drag.addClass('dragitem');
drag.addClass('item' + itemno);
drag.one('span.markertext').setStyle('opacity', 0.6);
draghome.insert(drag, 'after');
if (!this.get('readonly')) {
this.draggable(drag);
}
return drag;
},
draggable : function (drag) {
var dd = new Y.DD.Drag({
node: drag,
dragMode: 'intersect'
}).plug(Y.Plugin.DDConstrained, {constrain2node: this.doc.top_node()});
dd.after('drag:start', function(e){
var dragnode = e.target.get('node');
dragnode.addClass('beingdragged');
var choiceno = this.get_choiceno_for_node(dragnode);
var itemno = this.get_itemno_for_node(dragnode);
if (itemno !== null) {
dragnode.removeClass('item' + dragnode);
}
this.save_all_xy_for_choice(choiceno, null);
this.redraw_drags_and_drops();
}, this);
dd.after('drag:end', function(e) {
var dragnode = e.target.get('node');
dragnode.removeClass('beingdragged');
var choiceno = this.get_choiceno_for_node(dragnode);
this.save_all_xy_for_choice(choiceno, dragnode);
this.redraw_drags_and_drops();
}, this);
//--- keyboard accessibility
drag.set('tabIndex', 0);
drag.on('dragchange', this.drop_zone_key_press, this);
},
save_all_xy_for_choice: function (choiceno, dropped) {
var coords = [];
var bgimgxy;
for (var i = 0; i <= this.doc.drag_items_for_choice(choiceno).size(); i++) {
var dragitem = this.doc.drag_item_for_choice(choiceno, i);
if (dragitem) {
dragitem.removeClass('item' + i);
if (!dragitem.hasClass('beingdragged')) {
bgimgxy = this.convert_to_bg_img_xy(dragitem.getXY());
if (this.xy_in_bgimg(bgimgxy)) {
dragitem.removeClass('item' + i);
dragitem.addClass('item' + coords.length);
coords[coords.length] = bgimgxy;
}
}
}
}
if (dropped !== null){
bgimgxy = this.convert_to_bg_img_xy(dropped.getXY());
dropped.addClass('item' + coords.length);
if (this.xy_in_bgimg(bgimgxy)) {
coords[coords.length] = bgimgxy;
}
}
this.set_form_value(choiceno, coords.join(';'));
},
reset_drag_xy : function (choiceno) {
this.set_form_value(choiceno, '');
},
set_form_value : function (choiceno, value) {
this.doc.input_for_choice(choiceno).set('value', value);
},
//make sure xy value is not out of bounds of bg image
xy_in_bgimg : function (bgimgxy) {
if ((bgimgxy[0] < 0) ||
(bgimgxy[1] < 0) ||
(bgimgxy[0] > this.doc.bg_img().get('width')) ||
(bgimgxy[1] > this.doc.bg_img().get('height'))){
return false;
} else {
return true;
}
},
constrain_to_bgimg : function (windowxy) {
var bgimgxy = this.convert_to_bg_img_xy(windowxy);
bgimgxy[0] = Math.max(0, bgimgxy[0]);
bgimgxy[1] = Math.max(0, bgimgxy[1]);
bgimgxy[0] = Math.min(this.doc.bg_img().get('width'), bgimgxy[0]);
bgimgxy[1] = Math.min(this.doc.bg_img().get('height'), bgimgxy[1]);
return this.convert_to_window_xy(bgimgxy);
},
convert_to_bg_img_xy : function (windowxy) {
return [Number(windowxy[0]) - this.doc.bg_img().getX() - 1,
Number(windowxy[1]) - this.doc.bg_img().getY() - 1];
},
redraw_drags_and_drops : function() {
this.doc.drag_items().each(function(item) {
//if (!item.hasClass('beingdragged')){
item.addClass('unneeded');
//}
}, this);
this.doc.inputs_for_choices().each(function (input) {
var choiceno = this.get_choiceno_for_node(input);
var coords = this.get_coords(input);
var dragitemhome = this.doc.drag_item_home(choiceno);
for (var i = 0; i < coords.length; i++) {
var dragitem = this.doc.drag_item_for_choice(choiceno, i);
if (!dragitem || dragitem.hasClass('beingdragged')) {
dragitem = this.clone_new_drag_item(dragitemhome, i);
} else {
dragitem.removeClass('unneeded');
}
dragitem.setXY(coords[i]);
}
}, this);
this.doc.drag_items().each(function(item) {
if (item.hasClass('unneeded') && !item.hasClass('beingdragged')) {
item.remove(true);
}
}, this);
if (this.graphics !== null) {
this.graphics.clear();
} else {
this.graphics = new Y.Graphic(
{render:this.doc.top_node().one("div.ddarea div.dropzones")}
);
}
if (this.get('dropzones').length !== 0) {
this.restart_colours();
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, true);
}
}
},
/**
* Determine what drag items need to be shown and
* return coords of all drag items except any that are currently being dragged
* based on contents of hidden inputs and whether drags are 'infinite' or how many drags should be shown.
*/
get_coords : function (input) {
var choiceno = this.get_choiceno_for_node(input);
var fv = input.get('value');
var infinite = input.hasClass('infinite');
var noofdrags = this.get_noofdrags_for_node(input);
var dragging = (null !== this.doc.drag_item_being_dragged(choiceno));
var coords = [];
if (fv !== '') {
var coordsstrings = fv.split(';');
for (var i = 0; i < coordsstrings.length; i++) {
coords[coords.length] = this.convert_to_window_xy(coordsstrings[i].split(','));
}
}
var displayeddrags = coords.length + (dragging ? 1 : 0);
if (infinite || (displayeddrags < noofdrags)) {
coords[coords.length] = this.drag_home_xy(choiceno);
}
return coords;
},
drag_home_xy : function (choiceno) {
var dragitemhome = this.doc.drag_item_home(choiceno);
return [dragitemhome.getX(), dragitemhome.getY() - 12];
},
get_choiceno_for_node : function(node) {
return Number(this.doc.get_classname_numeric_suffix(node, 'choice'));
},
get_itemno_for_node : function(node) {
return Number(this.doc.get_classname_numeric_suffix(node, 'item'));
},
get_noofdrags_for_node : function(node) {
return Number(this.doc.get_classname_numeric_suffix(node, 'noofdrags'));
},
// Keyboard accessibility stuff below here.
drop_zone_key_press : function (e) {
var dragitem = e.target;
var xy = dragitem.getXY();
switch (e.direction) {
case 'left' :
xy[0] -= 1;
break;
case 'right' :
xy[0] += 1;
break;
case 'down' :
xy[1] += 1;
break;
case 'up' :
xy[1] -= 1;
break;
case 'remove' :
xy = null;
break;
}
var choiceno = this.get_choiceno_for_node(dragitem);
if (xy !== null) {
xy = this.constrain_to_bgimg(xy);
} else {
xy = this.drag_home_xy(choiceno);
}
e.preventDefault();
dragitem.setXY(xy);
this.save_all_xy_for_choice(choiceno, null);
}
}, {NAME : DDMARKERQUESTIONNAME, ATTRS : {dropzones:{value:[]}}});
Y.Event.define('dragchange', {
// Webkit and IE repeat keydown when you hold down arrow keys.
// Opera links keypress to page scroll; others keydown.
// Firefox prevents page scroll via preventDefault() on either
// keydown or keypress.
_event: (Y.UA.webkit || Y.UA.ie) ? 'keydown' : 'keypress',
_keys: {
'32': 'remove', // Space
'37': 'left', // Left arrow
'38': 'up', // Up arrow
'39': 'right', // Right arrow
'40': 'down', // Down arrow
'65': 'left', // a
'87': 'up', // w
'68': 'right', // d
'83': 'down', // s
'27': 'remove' // Escape
},
_keyHandler: function (e, notifier) {
if (this._keys[e.keyCode]) {
e.direction = this._keys[e.keyCode];
notifier.fire(e);
}
},
on: function (node, sub, notifier) {
sub._detacher = node.on(this._event, this._keyHandler,
this, notifier);
}
});
M.qtype_ddmarker.init_question = function(config) {
return new DDMARKER_QUESTION(config);
};
}, '@VERSION@', {
requires:['node', 'event-resize', 'dd', 'dd-drop', 'dd-constrain', 'graphics']
});

View file

@ -1,255 +0,0 @@
/**
* This is the question editing form code.
*/
YUI.add('moodle-qtype_ddmarker-form', function(Y) {
var DDMARKERFORMNAME = 'ddmarker_form';
var DDMARKER_FORM = function() {
DDMARKER_FORM.superclass.constructor.apply(this, arguments);
};
Y.extend(DDMARKER_FORM, M.qtype_ddmarker.dd_base_class, {
fp : null,
initializer : function() {
this.fp = this.file_pickers();
var tn = Y.one(this.get('topnode'));
tn.one('div.fcontainer').append(
'<div class="ddarea">' +
'<div class="markertexts"></div>' +
'<div class="droparea"></div>' +
'<div class="dropzones"></div>' +
'<div class="grid"></div>' +
'</div>');
this.doc = this.doc_structure(this);
this.stop_selector_events();
this.set_options_for_drag_item_selectors();
this.setup_form_events();
Y.later(500, this, this.update_drop_zones, [], true);
Y.after(this.load_bg_image, M.form_filepicker, 'callback', this);
this.load_bg_image();
},
load_bg_image : function() {
var bgimageurl = this.fp.file('bgimage').href;
if (bgimageurl !== null) {
this.doc.load_bg_img(bgimageurl);
var drop = new Y.DD.Drop({
node: this.doc.bg_img()
});
// Listen for a drop:hit on the background image.
drop.on('drop:hit', function(e) {
e.drag.get('node').setData('gooddrop', true);
});
this.afterimageloaddone = false;
this.doc.bg_img().on('load', this.constrain_image_size, this);
}
},
constrain_image_size : function (e) {
var maxsize = this.get('maxsizes').bgimage;
var reduceby = Math.max(e.target.get('width') / maxsize.width,
e.target.get('height') / maxsize.height);
if (reduceby > 1) {
e.target.set('width', Math.floor(e.target.get('width') / reduceby));
}
e.target.addClass('constrained');
e.target.detach('load', this.constrain_image_size);
},
update_drop_zones : function () {
// Set up drop zones.
if (this.graphics !== null) {
this.graphics.destroy();
}
this.restart_colours();
this.graphics = new Y.Graphic({render:"div.ddarea div.dropzones"});
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);
this.draw_drop_zone(dropzoneno, markertext,
shape, coords, colourfordropzone, false);
}
Y.one('div.ddarea .grid')
.setXY(this.doc.bg_img().getXY())
.setStyle('width', this.doc.bg_img().get('width'))
.setStyle('height', 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'), '');
},
get_marker_text : function (markerno) {
if (Number(markerno) !== 0) {
var label = this.form.get_form_value('drags', [markerno - 1, 'label']);
return label.replace(new RegExp("^\\s*(.*)\\s*$"), "$1");
} else {
return '';
}
},
set_options_for_drag_item_selectors : function () {
var dragitemsoptions = {0: ''};
for (var i = 1; i <= this.form.get_form_value('noitems', []); i++) {
var label = this.get_marker_text(i);
if (label !== "") {
dragitemsoptions[i] = Y.Escape.html(label);
}
}
// Get all the currently selected drags for each drop.
var selectedvalues = [];
var selector;
for (i = 0; i < this.form.get_form_value('nodropzone', []); i++) {
selector = Y.one('#id_drops_' + i + '_choice');
selectedvalues[i] = Number(selector.get('value'));
}
for (i = 0; i < this.form.get_form_value('nodropzone', []); i++) {
selector = Y.one('#id_drops_' + i + '_choice');
// Remove all options for drag choice.
selector.all('option').remove(true);
// And recreate the options.
for (var value in dragitemsoptions) {
value = Number(value);
var option = '<option value="' + value + '">' + dragitemsoptions[value] + '</option>';
selector.append(option);
var optionnode = selector.one('option[value="' + value + '"]');
// Is this the currently selected value?
if (value === selectedvalues[i]) {
optionnode.set('selected', true);
} else {
// It is not the currently selected value, is it selectable?
if (value !== 0) { // The 'no item' option is always selectable.
// Variables to hold form values about this drag item.
var noofdrags = this.form.get_form_value('drags', [value - 1, 'noofdrags']);
if (Number(noofdrags) !== 0) { // 'noofdrags == 0' means infinite.
// Go through all selected values in drop downs.
for (var k in selectedvalues) {
// Count down 'noofdrags' and if reach zero then set disabled option for this drag item.
if (Number(selectedvalues[k]) === value) {
if (Number(noofdrags) === 1) {
optionnode.set('disabled', true);
break;
} else {
noofdrags--;
}
}
}
}
}
}
}
}
},
stop_selector_events : function () {
Y.all('fieldset#id_dropzoneheader select').detachAll();
},
setup_form_events : function () {
//events triggered by changes to form data
// Changes to labels.
Y.all('fieldset#id_draggableitemheader input').on('change', function () {
this.set_options_for_drag_item_selectors();
}, this);
// Changes to selected drag item.
Y.all('fieldset#id_draggableitemheader select').on('change', function () {
this.set_options_for_drag_item_selectors();
}, this);
// Change in selected item.
Y.all('fieldset#id_dropzoneheader select').on('change', function () {
this.set_options_for_drag_item_selectors();
}, this);
},
/**
* Low level operations on form.
*/
form : {
to_name_with_index : function(name, indexes) {
var indexstring = name;
for (var i = 0; i < indexes.length; i++) {
indexstring = indexstring + '[' + indexes[i] + ']';
}
return indexstring;
},
get_el : function (name, indexes) {
var form = document.getElementById('mform1');
return form.elements[this.to_name_with_index(name, indexes)];
},
get_form_value : function(name, indexes) {
var el = this.get_el(name, indexes);
if (el.type === 'checkbox') {
return el.checked;
} else {
return el.value;
}
},
set_form_value : function(name, indexes, value) {
var el = this.get_el(name, indexes);
if (el.type === 'checkbox') {
el.checked = value;
} else {
el.value = value;
}
},
from_name_with_index : function(name) {
var toreturn = {};
toreturn.indexes = [];
var bracket = name.indexOf('[');
toreturn.name = name.substring(0, bracket);
while (bracket !== -1) {
var end = name.indexOf(']', bracket + 1);
toreturn.indexes.push(name.substring(bracket + 1, end));
bracket = name.indexOf('[', end + 1);
}
return toreturn;
}
},
file_pickers : function () {
var draftitemidstoname;
var nametoparentnode;
if (draftitemidstoname === undefined) {
draftitemidstoname = {};
nametoparentnode = {};
var filepickers = Y.all('form.mform input.filepickerhidden');
filepickers.each(function(filepicker) {
draftitemidstoname[filepicker.get('value')] = filepicker.get('name');
nametoparentnode[filepicker.get('name')] = filepicker.get('parentNode');
}, this);
}
var toreturn = {
file : function (name) {
var parentnode = nametoparentnode[name];
var fileanchor = parentnode.one('div.filepicker-filelist a');
if (fileanchor) {
return {href : fileanchor.get('href'), name : fileanchor.get('innerHTML')};
} else {
return {href : null, name : null};
}
},
name : function (draftitemid) {
return draftitemidstoname[draftitemid];
}
};
return toreturn;
}
}, {NAME : DDMARKERFORMNAME, ATTRS : {maxsizes:{value:null}}});
M.qtype_ddmarker = M.qtype_ddmarker || {};
M.qtype_ddmarker.init_form = function(config) {
return new DDMARKER_FORM(config);
};
}, '@VERSION@', {
requires:['moodle-qtype_ddmarker-dd', 'form_filepicker', 'graphics', 'escape']
});

View file

@ -0,0 +1,10 @@
{
"name": "moodle-qtype_ddmarker-dd",
"builds": {
"moodle-qtype_ddmarker-dd": {
"jsfiles": [
"ddmarker.js"
]
}
}
}

View file

@ -0,0 +1,560 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
var DDMARKERDDNAME = 'moodle-qtype_ddmarker-dd';
var DDMARKER_DD = function() {
DDMARKER_DD.superclass.constructor.apply(this, arguments);
};
/**
* This is the base class for the question rendering and question editing form code.
*/
Y.extend(DDMARKER_DD, Y.Base, {
doc : null,
polltimer : null,
afterimageloaddone : false,
graphics : null,
poll_for_image_load : function (e, waitforimageconstrain, pause, doafterwords) {
if (this.afterimageloaddone) {
return;
}
var bgdone = this.doc.bg_img().get('complete');
if (waitforimageconstrain) {
bgdone = bgdone && this.doc.bg_img().hasClass('constrained');
}
if (bgdone) {
if (this.polltimer !== null) {
this.polltimer.cancel();
this.polltimer = null;
}
this.doc.bg_img().detach('load', this.poll_for_image_load);
if (pause !== 0) {
Y.later(pause, this, doafterwords);
} else {
doafterwords.call(this);
}
this.afterimageloaddone = true;
} else if (this.polltimer === null) {
var pollarguments = [null, waitforimageconstrain, pause, doafterwords];
this.polltimer =
Y.later(1000, this, this.poll_for_image_load, pollarguments, true);
}
},
/**
* Object to encapsulate operations on dd area.
*/
doc_structure : function () {
var topnode = Y.one(this.get('topnode'));
var dragitemsarea = topnode.one('div.dragitems');
var dropbgarea = topnode.one('div.droparea');
return {
top_node : function() {
return topnode;
},
bg_img : function() {
return topnode.one('.dropbackground');
},
load_bg_img : function (url) {
dropbgarea.setContent('<img class="dropbackground" src="' + url + '"/>');
this.bg_img().on('load', this.on_image_load, this, 'bg_image');
},
drag_items : function() {
return dragitemsarea.all('.dragitem');
},
drag_items_for_choice : function(choiceno) {
return dragitemsarea.all('span.dragitem.choice' + choiceno);
},
drag_item_for_choice : function(choiceno, itemno) {
return dragitemsarea.one('span.dragitem.choice' + choiceno +
'.item' + itemno);
},
drag_item_being_dragged : function(choiceno) {
return dragitemsarea.one('span.dragitem.beingdragged.choice' + choiceno);
},
drag_item_home : function (choiceno) {
return dragitemsarea.one('span.draghome.choice' + choiceno);
},
drag_item_homes : function() {
return dragitemsarea.all('span.draghome');
},
get_classname_numeric_suffix : function(node, prefix) {
var classes = node.getAttribute('class');
if (classes !== '') {
var classesarr = classes.split(' ');
for (var index = 0; index < classesarr.length; index++) {
var patt1 = new RegExp('^' + prefix + '([0-9])+$');
if (patt1.test(classesarr[index])) {
var patt2 = new RegExp('([0-9])+$');
var match = patt2.exec(classesarr[index]);
return Number(match[0]);
}
}
}
return null;
},
inputs_for_choices : function () {
return topnode.all('input.choices');
},
input_for_choice : function (choiceno) {
return topnode.one('input.choice' + choiceno);
},
marker_texts : function () {
return topnode.one('div.markertexts');
}
};
},
colours : ['#FFFFFF', '#B0C4DE', '#DCDCDC', '#D8BFD8',
'#87CEFA','#DAA520', '#FFD700', '#F0E68C'],
nextcolourindex : 0,
restart_colours : function () {
this.nextcolourindex = 0;
},
get_next_colour : function () {
var colour = this.colours[this.nextcolourindex];
this.nextcolourindex++;
if (this.nextcolourindex === this.colours.length) {
this.nextcolourindex = 0;
}
return colour;
},
convert_to_window_xy : function (bgimgxy) {
return [Number(bgimgxy[0]) + this.doc.bg_img().getX() + 1,
Number(bgimgxy[1]) + this.doc.bg_img().getY() + 1];
},
shapes : [],
draw_drop_zone : function (dropzoneno, markertext, shape, coords, colour, link) {
var existingmarkertext;
if (link) {
existingmarkertext = this.doc.marker_texts().one('span.markertext' + dropzoneno + ' a');
} else {
existingmarkertext = this.doc.marker_texts().one('span.markertext' + dropzoneno);
}
if (existingmarkertext) {
if (markertext !== '') {
existingmarkertext.setContent(markertext);
} else {
existingmarkertext.remove(true);
}
} else if (markertext !== '') {
var classnames = 'markertext markertext' + dropzoneno;
if (link) {
this.doc.marker_texts().append('<span class="' + classnames + '"><a href="#">' +
markertext + '</a></span>');
} else {
this.doc.marker_texts().append('<span class="' + classnames + '">' +
markertext + '</span>');
}
}
var drawfunc = 'draw_shape_' + shape;
if (this[drawfunc] instanceof Function){
var xyfortext = this[drawfunc](dropzoneno, coords, colour);
if (xyfortext !== null) {
var markerspan = this.doc.top_node().one('div.ddarea div.markertexts span.markertext' + dropzoneno);
if (markerspan !== null) {
markerspan.setStyle('opacity', '0.6');
xyfortext[0] -= markerspan.get('offsetWidth') / 2;
xyfortext[1] -= markerspan.get('offsetHeight') / 2;
markerspan.setXY(this.convert_to_window_xy(xyfortext));
var markerspananchor = markerspan.one('a');
if (markerspananchor !== null) {
markerspananchor.once('click', function (e, dropzoneno) {
var fill = this.shapes[dropzoneno].get('fill');
fill.opacity = 1;
this.shapes[dropzoneno].set('fill', fill);
},
this,
dropzoneno
);
markerspananchor.set('tabIndex', 0);
}
}
}
}
},
draw_shape_circle : function (dropzoneno, coords, colour) {
var coordsparts = coords.match(/(\d+),(\d+);(\d+)/);
if (coordsparts && coordsparts.length === 4) {
var xy = [Number(coordsparts[1]) - coordsparts[3], Number(coordsparts[2]) - coordsparts[3]];
if (this.coords_in_img(xy)) {
var widthheight = [Number(coordsparts[3]) * 2, Number(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 [Number(coordsparts[1]), Number(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 = [Number(coordsparts[1]), Number(coordsparts[2])];
var widthheight = [Number(coordsparts[3]), Number(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 [Number(xy[0]) + widthheight[0] / 2, Number(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 (i = 0; i < xy.length; i++) {
//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 (Number(xy[0][0]) !== Number(xy[xy.length - 1][0]) || Number(xy[0][1]) !== Number(xy[xy.length - 1][1])) {
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 [(minxy[0] + maxxy[0]) / 2, (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,
ATTRS : {
drops : {value : null},
readonly : {value : false},
topnode : {value : null}
}
});
M.qtype_ddmarker = M.qtype_ddmarker || {};
M.qtype_ddmarker.dd_base_class = DDMARKER_DD;
var DDMARKERQUESTIONNAME = 'ddmarker_question';
var DDMARKER_QUESTION = function() {
DDMARKER_QUESTION.superclass.constructor.apply(this, arguments);
};
/**
* This is the code for question rendering.
*/
Y.extend(DDMARKER_QUESTION, M.qtype_ddmarker.dd_base_class, {
initializer : function() {
this.doc = this.doc_structure(this);
this.poll_for_image_load(null, false, 0, this.after_image_load);
this.doc.bg_img().after('load', this.poll_for_image_load, this,
false, 0, this.after_image_load);
},
after_image_load : function () {
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);
drag.removeClass('draghome');
drag.addClass('dragitem');
drag.addClass('item' + itemno);
drag.one('span.markertext').setStyle('opacity', 0.6);
draghome.insert(drag, 'after');
if (!this.get('readonly')) {
this.draggable(drag);
}
return drag;
},
draggable : function (drag) {
var dd = new Y.DD.Drag({
node: drag,
dragMode: 'intersect'
}).plug(Y.Plugin.DDConstrained, {constrain2node: this.doc.top_node()});
dd.after('drag:start', function(e){
var dragnode = e.target.get('node');
dragnode.addClass('beingdragged');
var choiceno = this.get_choiceno_for_node(dragnode);
var itemno = this.get_itemno_for_node(dragnode);
if (itemno !== null) {
dragnode.removeClass('item' + dragnode);
}
this.save_all_xy_for_choice(choiceno, null);
this.redraw_drags_and_drops();
}, this);
dd.after('drag:end', function(e) {
var dragnode = e.target.get('node');
dragnode.removeClass('beingdragged');
var choiceno = this.get_choiceno_for_node(dragnode);
this.save_all_xy_for_choice(choiceno, dragnode);
this.redraw_drags_and_drops();
}, this);
//--- keyboard accessibility
drag.set('tabIndex', 0);
drag.on('dragchange', this.drop_zone_key_press, this);
},
save_all_xy_for_choice: function (choiceno, dropped) {
var coords = [];
var bgimgxy;
for (var i = 0; i <= this.doc.drag_items_for_choice(choiceno).size(); i++) {
var dragitem = this.doc.drag_item_for_choice(choiceno, i);
if (dragitem) {
dragitem.removeClass('item' + i);
if (!dragitem.hasClass('beingdragged')) {
bgimgxy = this.convert_to_bg_img_xy(dragitem.getXY());
if (this.xy_in_bgimg(bgimgxy)) {
dragitem.removeClass('item' + i);
dragitem.addClass('item' + coords.length);
coords[coords.length] = bgimgxy;
}
}
}
}
if (dropped !== null){
bgimgxy = this.convert_to_bg_img_xy(dropped.getXY());
dropped.addClass('item' + coords.length);
if (this.xy_in_bgimg(bgimgxy)) {
coords[coords.length] = bgimgxy;
}
}
this.set_form_value(choiceno, coords.join(';'));
},
reset_drag_xy : function (choiceno) {
this.set_form_value(choiceno, '');
},
set_form_value : function (choiceno, value) {
this.doc.input_for_choice(choiceno).set('value', value);
},
//make sure xy value is not out of bounds of bg image
xy_in_bgimg : function (bgimgxy) {
if ((bgimgxy[0] < 0) ||
(bgimgxy[1] < 0) ||
(bgimgxy[0] > this.doc.bg_img().get('width')) ||
(bgimgxy[1] > this.doc.bg_img().get('height'))){
return false;
} else {
return true;
}
},
constrain_to_bgimg : function (windowxy) {
var bgimgxy = this.convert_to_bg_img_xy(windowxy);
bgimgxy[0] = Math.max(0, bgimgxy[0]);
bgimgxy[1] = Math.max(0, bgimgxy[1]);
bgimgxy[0] = Math.min(this.doc.bg_img().get('width'), bgimgxy[0]);
bgimgxy[1] = Math.min(this.doc.bg_img().get('height'), bgimgxy[1]);
return this.convert_to_window_xy(bgimgxy);
},
convert_to_bg_img_xy : function (windowxy) {
return [Number(windowxy[0]) - this.doc.bg_img().getX() - 1,
Number(windowxy[1]) - this.doc.bg_img().getY() - 1];
},
redraw_drags_and_drops : function() {
this.doc.drag_items().each(function(item) {
//if (!item.hasClass('beingdragged')){
item.addClass('unneeded');
//}
}, this);
this.doc.inputs_for_choices().each(function (input) {
var choiceno = this.get_choiceno_for_node(input);
var coords = this.get_coords(input);
var dragitemhome = this.doc.drag_item_home(choiceno);
for (var i = 0; i < coords.length; i++) {
var dragitem = this.doc.drag_item_for_choice(choiceno, i);
if (!dragitem || dragitem.hasClass('beingdragged')) {
dragitem = this.clone_new_drag_item(dragitemhome, i);
} else {
dragitem.removeClass('unneeded');
}
dragitem.setXY(coords[i]);
}
}, this);
this.doc.drag_items().each(function(item) {
if (item.hasClass('unneeded') && !item.hasClass('beingdragged')) {
item.remove(true);
}
}, this);
if (this.graphics !== null) {
this.graphics.clear();
} else {
this.graphics = new Y.Graphic(
{render:this.doc.top_node().one("div.ddarea div.dropzones")}
);
}
if (this.get('dropzones').length !== 0) {
this.restart_colours();
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, true);
}
}
},
/**
* Determine what drag items need to be shown and
* return coords of all drag items except any that are currently being dragged
* based on contents of hidden inputs and whether drags are 'infinite' or how many drags should be shown.
*/
get_coords : function (input) {
var choiceno = this.get_choiceno_for_node(input);
var fv = input.get('value');
var infinite = input.hasClass('infinite');
var noofdrags = this.get_noofdrags_for_node(input);
var dragging = (null !== this.doc.drag_item_being_dragged(choiceno));
var coords = [];
if (fv !== '') {
var coordsstrings = fv.split(';');
for (var i = 0; i < coordsstrings.length; i++) {
coords[coords.length] = this.convert_to_window_xy(coordsstrings[i].split(','));
}
}
var displayeddrags = coords.length + (dragging ? 1 : 0);
if (infinite || (displayeddrags < noofdrags)) {
coords[coords.length] = this.drag_home_xy(choiceno);
}
return coords;
},
drag_home_xy : function (choiceno) {
var dragitemhome = this.doc.drag_item_home(choiceno);
return [dragitemhome.getX(), dragitemhome.getY() - 12];
},
get_choiceno_for_node : function(node) {
return Number(this.doc.get_classname_numeric_suffix(node, 'choice'));
},
get_itemno_for_node : function(node) {
return Number(this.doc.get_classname_numeric_suffix(node, 'item'));
},
get_noofdrags_for_node : function(node) {
return Number(this.doc.get_classname_numeric_suffix(node, 'noofdrags'));
},
// Keyboard accessibility stuff below here.
drop_zone_key_press : function (e) {
var dragitem = e.target;
var xy = dragitem.getXY();
switch (e.direction) {
case 'left' :
xy[0] -= 1;
break;
case 'right' :
xy[0] += 1;
break;
case 'down' :
xy[1] += 1;
break;
case 'up' :
xy[1] -= 1;
break;
case 'remove' :
xy = null;
break;
}
var choiceno = this.get_choiceno_for_node(dragitem);
if (xy !== null) {
xy = this.constrain_to_bgimg(xy);
} else {
xy = this.drag_home_xy(choiceno);
}
e.preventDefault();
dragitem.setXY(xy);
this.save_all_xy_for_choice(choiceno, null);
}
}, {NAME : DDMARKERQUESTIONNAME, ATTRS : {dropzones:{value:[]}}});
Y.Event.define('dragchange', {
// Webkit and IE repeat keydown when you hold down arrow keys.
// Opera links keypress to page scroll; others keydown.
// Firefox prevents page scroll via preventDefault() on either
// keydown or keypress.
_event: (Y.UA.webkit || Y.UA.ie) ? 'keydown' : 'keypress',
_keys: {
'32': 'remove', // Space
'37': 'left', // Left arrow
'38': 'up', // Up arrow
'39': 'right', // Right arrow
'40': 'down', // Down arrow
'65': 'left', // a
'87': 'up', // w
'68': 'right', // d
'83': 'down', // s
'27': 'remove' // Escape
},
_keyHandler: function (e, notifier) {
if (this._keys[e.keyCode]) {
e.direction = this._keys[e.keyCode];
notifier.fire(e);
}
},
on: function (node, sub, notifier) {
sub._detacher = node.on(this._event, this._keyHandler,
this, notifier);
}
});
M.qtype_ddmarker.init_question = function(config) {
return new DDMARKER_QUESTION(config);
};

View file

@ -0,0 +1,12 @@
{
"moodle-qtype_ddmarker-dd": {
"requires": [
"node",
"event-resize",
"dd",
"dd-drop",
"dd-constrain",
"graphics"
]
}
}

View file

@ -0,0 +1,10 @@
{
"name": "moodle-qtype_ddmarker-form",
"builds": {
"moodle-qtype_ddmarker-form": {
"jsfiles": [
"form.js"
]
}
}
}

View file

@ -0,0 +1,267 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This is the question editing form code.
*/
var DDMARKERFORMNAME = 'moodle-qtype_ddmarker-form';
var DDMARKER_FORM = function() {
DDMARKER_FORM.superclass.constructor.apply(this, arguments);
};
Y.extend(DDMARKER_FORM, M.qtype_ddmarker.dd_base_class, {
fp : null,
initializer : function() {
this.fp = this.file_pickers();
var tn = Y.one(this.get('topnode'));
tn.one('div.fcontainer').append(
'<div class="ddarea">' +
'<div class="markertexts"></div>' +
'<div class="droparea"></div>' +
'<div class="dropzones"></div>' +
'<div class="grid"></div>' +
'</div>');
this.doc = this.doc_structure(this);
this.stop_selector_events();
this.set_options_for_drag_item_selectors();
this.setup_form_events();
Y.later(500, this, this.update_drop_zones, [], true);
Y.after(this.load_bg_image, M.form_filepicker, 'callback', this);
this.load_bg_image();
},
load_bg_image : function() {
var bgimageurl = this.fp.file('bgimage').href;
if (bgimageurl !== null) {
this.doc.load_bg_img(bgimageurl);
var drop = new Y.DD.Drop({
node: this.doc.bg_img()
});
// Listen for a drop:hit on the background image.
drop.on('drop:hit', function(e) {
e.drag.get('node').setData('gooddrop', true);
});
this.afterimageloaddone = false;
this.doc.bg_img().on('load', this.constrain_image_size, this);
}
},
constrain_image_size : function (e) {
var maxsize = this.get('maxsizes').bgimage;
var reduceby = Math.max(e.target.get('width') / maxsize.width,
e.target.get('height') / maxsize.height);
if (reduceby > 1) {
e.target.set('width', Math.floor(e.target.get('width') / reduceby));
}
e.target.addClass('constrained');
e.target.detach('load', this.constrain_image_size);
},
update_drop_zones : function () {
// Set up drop zones.
if (this.graphics !== null) {
this.graphics.destroy();
}
this.restart_colours();
this.graphics = new Y.Graphic({render:"div.ddarea div.dropzones"});
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);
this.draw_drop_zone(dropzoneno, markertext,
shape, coords, colourfordropzone, false);
}
Y.one('div.ddarea .grid')
.setXY(this.doc.bg_img().getXY())
.setStyle('width', this.doc.bg_img().get('width'))
.setStyle('height', 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'), '');
},
get_marker_text : function (markerno) {
if (Number(markerno) !== 0) {
var label = this.form.get_form_value('drags', [markerno - 1, 'label']);
return label.replace(new RegExp("^\\s*(.*)\\s*$"), "$1");
} else {
return '';
}
},
set_options_for_drag_item_selectors : function () {
var dragitemsoptions = {0: ''};
for (var i = 1; i <= this.form.get_form_value('noitems', []); i++) {
var label = this.get_marker_text(i);
if (label !== "") {
dragitemsoptions[i] = Y.Escape.html(label);
}
}
// Get all the currently selected drags for each drop.
var selectedvalues = [];
var selector;
for (i = 0; i < this.form.get_form_value('nodropzone', []); i++) {
selector = Y.one('#id_drops_' + i + '_choice');
selectedvalues[i] = Number(selector.get('value'));
}
for (i = 0; i < this.form.get_form_value('nodropzone', []); i++) {
selector = Y.one('#id_drops_' + i + '_choice');
// Remove all options for drag choice.
selector.all('option').remove(true);
// And recreate the options.
for (var value in dragitemsoptions) {
value = Number(value);
var option = '<option value="' + value + '">' + dragitemsoptions[value] + '</option>';
selector.append(option);
var optionnode = selector.one('option[value="' + value + '"]');
// Is this the currently selected value?
if (value === selectedvalues[i]) {
optionnode.set('selected', true);
} else {
// It is not the currently selected value, is it selectable?
if (value !== 0) { // The 'no item' option is always selectable.
// Variables to hold form values about this drag item.
var noofdrags = this.form.get_form_value('drags', [value - 1, 'noofdrags']);
if (Number(noofdrags) !== 0) { // 'noofdrags == 0' means infinite.
// Go through all selected values in drop downs.
for (var k in selectedvalues) {
// Count down 'noofdrags' and if reach zero then set disabled option for this drag item.
if (Number(selectedvalues[k]) === value) {
if (Number(noofdrags) === 1) {
optionnode.set('disabled', true);
break;
} else {
noofdrags--;
}
}
}
}
}
}
}
}
},
stop_selector_events : function () {
Y.all('fieldset#id_dropzoneheader select').detachAll();
},
setup_form_events : function () {
//events triggered by changes to form data
// Changes to labels.
Y.all('fieldset#id_draggableitemheader input').on('change', function () {
this.set_options_for_drag_item_selectors();
}, this);
// Changes to selected drag item.
Y.all('fieldset#id_draggableitemheader select').on('change', function () {
this.set_options_for_drag_item_selectors();
}, this);
// Change in selected item.
Y.all('fieldset#id_dropzoneheader select').on('change', function () {
this.set_options_for_drag_item_selectors();
}, this);
},
/**
* Low level operations on form.
*/
form : {
to_name_with_index : function(name, indexes) {
var indexstring = name;
for (var i = 0; i < indexes.length; i++) {
indexstring = indexstring + '[' + indexes[i] + ']';
}
return indexstring;
},
get_el : function (name, indexes) {
var form = document.getElementById('mform1');
return form.elements[this.to_name_with_index(name, indexes)];
},
get_form_value : function(name, indexes) {
var el = this.get_el(name, indexes);
if (el.type === 'checkbox') {
return el.checked;
} else {
return el.value;
}
},
set_form_value : function(name, indexes, value) {
var el = this.get_el(name, indexes);
if (el.type === 'checkbox') {
el.checked = value;
} else {
el.value = value;
}
},
from_name_with_index : function(name) {
var toreturn = {};
toreturn.indexes = [];
var bracket = name.indexOf('[');
toreturn.name = name.substring(0, bracket);
while (bracket !== -1) {
var end = name.indexOf(']', bracket + 1);
toreturn.indexes.push(name.substring(bracket + 1, end));
bracket = name.indexOf('[', end + 1);
}
return toreturn;
}
},
file_pickers : function () {
var draftitemidstoname;
var nametoparentnode;
if (draftitemidstoname === undefined) {
draftitemidstoname = {};
nametoparentnode = {};
var filepickers = Y.all('form.mform input.filepickerhidden');
filepickers.each(function(filepicker) {
draftitemidstoname[filepicker.get('value')] = filepicker.get('name');
nametoparentnode[filepicker.get('name')] = filepicker.get('parentNode');
}, this);
}
var toreturn = {
file : function (name) {
var parentnode = nametoparentnode[name];
var fileanchor = parentnode.one('div.filepicker-filelist a');
if (fileanchor) {
return {href : fileanchor.get('href'), name : fileanchor.get('innerHTML')};
} else {
return {href : null, name : null};
}
},
name : function (draftitemid) {
return draftitemidstoname[draftitemid];
}
};
return toreturn;
}
},{NAME : DDMARKERFORMNAME, ATTRS : {maxsizes:{value:null}}});
M.qtype_ddmarker = M.qtype_ddmarker || {};
M.qtype_ddmarker.init_form = function(config) {
return new DDMARKER_FORM(config);
};

View file

@ -0,0 +1,10 @@
{
"moodle-qtype_ddmarker-form": {
"requires": [
"moodle-qtype_ddmarker-dd",
"form_filepicker",
"graphics",
"escape"
]
}
}