Merge pull request #1947 from edx/valera/visual_ui_image_response
Improvement and update for ImageInput
This commit is contained in:
@@ -1,24 +1,60 @@
|
||||
<span>
|
||||
<input type="hidden" class="imageinput" src="${src}" name="input_${id}" id="input_${id}" value="${value}" />
|
||||
<div id="imageinput_${id}" onclick="image_input_click('${id}',event);" style = "background-image:url('${src}');width:${width}px;height:${height}px;position: relative; left: 0; top: 0;">
|
||||
<img src="${STATIC_URL}green-pointer.png" id="cross_${id}" style="position: absolute;top: ${gy}px;left: ${gx}px;" />
|
||||
</div>
|
||||
<input
|
||||
type="hidden"
|
||||
class="imageinput"
|
||||
src="${src}"
|
||||
name="input_${id}"
|
||||
id="input_${id}"
|
||||
value="${value}"
|
||||
/>
|
||||
|
||||
% if status == 'unsubmitted':
|
||||
<span class="unanswered" style="display:inline-block;" id="status_${id}" aria-describedby="input_${id}">
|
||||
<span class="sr">Status: unanswered</span>
|
||||
</span>
|
||||
% elif status == 'correct':
|
||||
<span class="correct" id="status_${id}" aria-describedby="input_${id}">
|
||||
<span class="sr">Status: correct</span>
|
||||
</span>
|
||||
% elif status == 'incorrect':
|
||||
<span class="incorrect" id="status_${id}" aria-describedby="input_${id}">
|
||||
<span class="sr">Status: incorrect</span>
|
||||
</span>
|
||||
% elif status == 'incomplete':
|
||||
<span class="incorrect" id="status_${id}" aria-describedby="input_${id}">
|
||||
<span class="sr">Status: incorrect</span>
|
||||
</span>
|
||||
% endif
|
||||
<div
|
||||
id="imageinput_${id}"
|
||||
style="background-image: url('${src}'); width: ${width}px; height: ${height}px; position: relative; left: 0; top: 0;"
|
||||
>
|
||||
<img
|
||||
src="${STATIC_URL}green-pointer.png"
|
||||
id="cross_${id}"
|
||||
style="position: absolute; top: ${gy}px; left: ${gx}px;"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
(new ImageInput('${id}'));
|
||||
</script>
|
||||
|
||||
% if status == 'unsubmitted':
|
||||
<span
|
||||
class="unanswered"
|
||||
style="display: inline-block;"
|
||||
id="status_${id}"
|
||||
aria-describedby="input_${id}"
|
||||
>
|
||||
<span class="sr">Status: unanswered</span>
|
||||
</span>
|
||||
% elif status == 'correct':
|
||||
<span
|
||||
class="correct"
|
||||
id="status_${id}"
|
||||
aria-describedby="input_${id}"
|
||||
>
|
||||
<span class="sr">Status: correct</span>
|
||||
</span>
|
||||
% elif status == 'incorrect':
|
||||
<span
|
||||
class="incorrect"
|
||||
id="status_${id}"
|
||||
aria-describedby="input_${id}"
|
||||
>
|
||||
<span class="sr">Status: incorrect</span>
|
||||
</span>
|
||||
% elif status == 'incomplete':
|
||||
<span
|
||||
class="incorrect"
|
||||
id="status_${id}"
|
||||
aria-describedby="input_${id}"
|
||||
>
|
||||
<span class="sr">Status: incorrect</span>
|
||||
</span>
|
||||
% endif
|
||||
</span>
|
||||
|
||||
31
common/lib/xmodule/xmodule/js/fixtures/imageinput.html
Normal file
31
common/lib/xmodule/xmodule/js/fixtures/imageinput.html
Normal file
@@ -0,0 +1,31 @@
|
||||
<!-- ${id} = 12345 -->
|
||||
<!-- ${width} = 300 -->
|
||||
<!-- ${height} = 400 -->
|
||||
|
||||
<span>
|
||||
<input
|
||||
type="hidden"
|
||||
class="imageinput"
|
||||
src=""
|
||||
name="input_12345"
|
||||
id="input_12345"
|
||||
value=""
|
||||
/>
|
||||
|
||||
<div
|
||||
id="imageinput_12345"
|
||||
style="width: 300px; height: 400px; position: relative; left: 0; top: 0; visibility: hidden;"
|
||||
>
|
||||
<!-- image will go here -->
|
||||
</div>
|
||||
|
||||
<!-- status == 'unsubmitted' -->
|
||||
<span
|
||||
class="unanswered"
|
||||
style="display: inline-block;"
|
||||
id="status_12345"
|
||||
aria-describedby="input_12345"
|
||||
>
|
||||
<span class="sr">Status: unanswered</span>
|
||||
</span>
|
||||
</span>
|
||||
145
common/lib/xmodule/xmodule/js/spec/capa/imageinput_spec.js
Normal file
145
common/lib/xmodule/xmodule/js/spec/capa/imageinput_spec.js
Normal file
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* "Beware of bugs in the above code; I have only proved it correct, not tried
|
||||
* it."
|
||||
*
|
||||
* ~ Donald Knuth
|
||||
*/
|
||||
|
||||
(function ($, ImageInput, undefined) {
|
||||
describe('ImageInput', function () {
|
||||
var state;
|
||||
|
||||
beforeEach(function () {
|
||||
var el;
|
||||
|
||||
loadFixtures('imageinput.html');
|
||||
el = $('#imageinput_12345');
|
||||
|
||||
el.append(createTestImage('cross_12345', 300, 400, 'red'));
|
||||
|
||||
state = new ImageInput('12345');
|
||||
|
||||
spyOn(state, 'clickHandler').andCallThrough();
|
||||
});
|
||||
|
||||
it('initialization', function () {
|
||||
expect(state.elementId).toBe('12345');
|
||||
|
||||
// Check that object's properties are present, and that the DOM
|
||||
// elements they reference exist.
|
||||
expect(state.el).toBeDefined();
|
||||
expect(state.el).toExist();
|
||||
|
||||
expect(state.crossEl).toBeDefined();
|
||||
expect(state.crossEl).toExist();
|
||||
|
||||
expect(state.inputEl).toBeDefined();
|
||||
expect(state.inputEl).toExist();
|
||||
|
||||
// Check that the click handler has been attached to the `state.el`
|
||||
// element. Note that `state.clickHandler()` method is called from
|
||||
// within the attached handler. That is why we can't use
|
||||
// Jasmine-jQuery `toHandleWith()` method.
|
||||
state.el.click();
|
||||
expect(state.clickHandler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('cross becomes visible after first click', function () {
|
||||
expect(state.crossEl.css('visibility')).toBe('hidden');
|
||||
|
||||
state.el.click();
|
||||
|
||||
expect(state.crossEl.css('visibility')).toBe('visible');
|
||||
});
|
||||
|
||||
it('coordinates are updated [offsetX is set]', function () {
|
||||
var event, posX, posY, cssLeft, cssTop;
|
||||
|
||||
// Set up of 'click' event.
|
||||
event = jQuery.Event(
|
||||
'click',
|
||||
{offsetX: 35.3, offsetY: 42.7}
|
||||
);
|
||||
|
||||
// Calculating the expected coordinates.
|
||||
posX = event.offsetX;
|
||||
posY = event.offsetY;
|
||||
|
||||
// Triggering 'click' event.
|
||||
jQuery(state.el).trigger(event);
|
||||
|
||||
// Getting actual (new) coordinates, and testing them against the
|
||||
// expected.
|
||||
cssLeft = stripPx(state.crossEl.css('left'));
|
||||
cssTop = stripPx(state.crossEl.css('top'));
|
||||
|
||||
expect(cssLeft).toBeCloseTo(posX - 15, 1);
|
||||
expect(cssTop).toBeCloseTo(posY - 15, 1);
|
||||
expect(state.inputEl.val()).toBe(
|
||||
'[' + Math.round(posX) + ',' + Math.round(posY) + ']'
|
||||
);
|
||||
});
|
||||
|
||||
it('coordinates are updated [offsetX is NOT set]', function () {
|
||||
var event, posX, posY, cssLeft, cssTop;
|
||||
|
||||
// Set up of 'click' event.
|
||||
event = jQuery.Event(
|
||||
'click',
|
||||
{
|
||||
offsetX: undefined, offsetY: undefined,
|
||||
pageX: 35.3, pageY: 42.7
|
||||
}
|
||||
);
|
||||
state.el[0].offsetLeft = 12;
|
||||
state.el[0].offsetTop = 3;
|
||||
|
||||
// Calculating the expected coordinates.
|
||||
posX = event.pageX - state.el[0].offsetLeft;
|
||||
posY = event.pageY - state.el[0].offsetTop;
|
||||
|
||||
// Triggering 'click' event.
|
||||
jQuery(state.el).trigger(event);
|
||||
|
||||
// Getting actual (new) coordinates, and testing them against the
|
||||
// expected.
|
||||
cssLeft = stripPx(state.crossEl.css('left'));
|
||||
cssTop = stripPx(state.crossEl.css('top'));
|
||||
|
||||
expect(cssLeft).toBeCloseTo(posX - 15, 1);
|
||||
expect(cssTop).toBeCloseTo(posY - 15, 1);
|
||||
expect(state.inputEl.val()).toBe(
|
||||
'[' + Math.round(posX) + ',' + Math.round(posY) + ']'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Instead of storing an image, and then including it in the template via
|
||||
// the <img /> tag, we will generate one on the fly.
|
||||
//
|
||||
// Create a simple image from a canvas. The canvas is filled by a colored
|
||||
// rectangle.
|
||||
function createTestImage(id, width, height, fillStyle) {
|
||||
var canvas, ctx, img;
|
||||
|
||||
canvas = document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
ctx = canvas.getContext('2d');
|
||||
ctx.fillStyle = fillStyle;
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
img = document.createElement('img');
|
||||
img.src = canvas.toDataURL('image/png');
|
||||
img.id = id;
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
// Strip the trailing 'px' substring from a CSS string containing the
|
||||
// `left` and `top` properties of an element's style.
|
||||
function stripPx(str) {
|
||||
return str.substring(0, str.length - 2);
|
||||
}
|
||||
}).call(this, window.jQuery, window.ImageInput);
|
||||
@@ -13,24 +13,49 @@
|
||||
* ~ Chinese Proverb
|
||||
*/
|
||||
|
||||
window.image_input_click = function (id, event) {
|
||||
var iiDiv = document.getElementById('imageinput_' + id),
|
||||
window.ImageInput = (function ($, undefined) {
|
||||
var ImageInput = ImageInputConstructor;
|
||||
|
||||
posX = event.offsetX ? event.offsetX : event.pageX - iiDiv.offsetLeft,
|
||||
posY = event.offsetY ? event.offsetY : event.pageY - iiDiv.offsetTop,
|
||||
ImageInput.prototype = {
|
||||
constructor: ImageInputConstructor,
|
||||
clickHandler: clickHandler
|
||||
};
|
||||
|
||||
cross = document.getElementById('cross_' + id),
|
||||
return ImageInput;
|
||||
|
||||
// To reduce differences between values returned by different kinds of
|
||||
// browsers, we round `posX` and `posY`.
|
||||
//
|
||||
// IE10: `posX` and `posY` - float.
|
||||
// Chrome, FF: `posX` and `posY` - integers.
|
||||
result = '[' + Math.round(posX) + ',' + Math.round(posY) + ']';
|
||||
function ImageInputConstructor(elementId) {
|
||||
var _this = this;
|
||||
|
||||
cross.style.left = (posX - 15) + 'px';
|
||||
cross.style.top = (posY - 15) + 'px';
|
||||
cross.style.visibility = 'visible';
|
||||
this.elementId = elementId;
|
||||
|
||||
document.getElementById('input_' + id).value = result;
|
||||
};
|
||||
this.el = $('#imageinput_' + this.elementId);
|
||||
this.crossEl = $('#cross_' + this.elementId);
|
||||
this.inputEl = $('#input_' + this.elementId);
|
||||
|
||||
this.el.on('click', function (event) {
|
||||
_this.clickHandler(event);
|
||||
});
|
||||
}
|
||||
|
||||
function clickHandler(event) {
|
||||
var posX = event.offsetX ?
|
||||
event.offsetX : event.pageX - this.el[0].offsetLeft,
|
||||
posY = event.offsetY ?
|
||||
event.offsetY : event.pageY - this.el[0].offsetTop,
|
||||
|
||||
// To reduce differences between values returned by different kinds
|
||||
// of browsers, we round `posX` and `posY`.
|
||||
//
|
||||
// IE10: `posX` and `posY` - float.
|
||||
// Chrome, FF: `posX` and `posY` - integers.
|
||||
result = '[' + Math.round(posX) + ',' + Math.round(posY) + ']';
|
||||
|
||||
this.crossEl.css({
|
||||
left: posX - 15,
|
||||
top: posY - 15,
|
||||
visibility: 'visible'
|
||||
});
|
||||
|
||||
this.inputEl.val(result);
|
||||
}
|
||||
}).call(this, window.jQuery);
|
||||
|
||||
Reference in New Issue
Block a user