From 7ebeccd4bf195c498478f73406c46c2c1d90f837 Mon Sep 17 00:00:00 2001 From: Valera Rozuvan Date: Thu, 12 Dec 2013 14:20:17 +0200 Subject: [PATCH 1/3] Updating ImageInput JS code base. --- .../lib/capa/capa/templates/imageinput.html | 78 ++++++++++++++----- .../xmodule/xmodule/js/src/capa/imageinput.js | 58 ++++++++++---- 2 files changed, 99 insertions(+), 37 deletions(-) diff --git a/common/lib/capa/capa/templates/imageinput.html b/common/lib/capa/capa/templates/imageinput.html index 394f180d3e93..77977a8d1221 100644 --- a/common/lib/capa/capa/templates/imageinput.html +++ b/common/lib/capa/capa/templates/imageinput.html @@ -1,24 +1,60 @@ - -
- -
+ - % if status == 'unsubmitted': - - Status: unanswered - - % elif status == 'correct': - - Status: correct - - % elif status == 'incorrect': - - Status: incorrect - - % elif status == 'incomplete': - - Status: incorrect - - % endif +
+ +
+ + + + % if status == 'unsubmitted': + + Status: unanswered + + % elif status == 'correct': + + Status: correct + + % elif status == 'incorrect': + + Status: incorrect + + % elif status == 'incomplete': + + Status: incorrect + + % endif
diff --git a/common/lib/xmodule/xmodule/js/src/capa/imageinput.js b/common/lib/xmodule/xmodule/js/src/capa/imageinput.js index 518cd91bba95..059ff65dd3c0 100644 --- a/common/lib/xmodule/xmodule/js/src/capa/imageinput.js +++ b/common/lib/xmodule/xmodule/js/src/capa/imageinput.js @@ -13,24 +13,50 @@ * ~ 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; + this.el = $('#imageinput_' + elementId); - document.getElementById('input_' + id).value = result; -}; + this.el.on('click', function (event) { + _this.clickHandler(event); + }); + } + + function clickHandler(event) { + var iiDiv = document.getElementById('imageinput_' + this.elementId), + + posX = event.offsetX ? + event.offsetX : event.pageX - iiDiv.offsetLeft, + posY = event.offsetY ? + event.offsetY : event.pageY - iiDiv.offsetTop, + + cross = document.getElementById('cross_' + this.elementId), + + // 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) + ']'; + + console.log('[image_input_click]: event = ', event); + + cross.style.left = (posX - 15) + 'px'; + cross.style.top = (posY - 15) + 'px'; + cross.style.visibility = 'visible'; + + document.getElementById('input_' + this.elementId).value = result; + } +}).call(this, window.jQuery); From 06b6535fb168de103ac8fa4e9542270905911b06 Mon Sep 17 00:00:00 2001 From: Valera Rozuvan Date: Fri, 13 Dec 2013 13:04:18 +0200 Subject: [PATCH 2/3] Using jQuery for all DOM manipulation. --- .../xmodule/xmodule/js/src/capa/imageinput.js | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/capa/imageinput.js b/common/lib/xmodule/xmodule/js/src/capa/imageinput.js index 059ff65dd3c0..d5df6e2ba61d 100644 --- a/common/lib/xmodule/xmodule/js/src/capa/imageinput.js +++ b/common/lib/xmodule/xmodule/js/src/capa/imageinput.js @@ -27,7 +27,10 @@ window.ImageInput = (function ($, undefined) { var _this = this; this.elementId = elementId; - this.el = $('#imageinput_' + elementId); + + this.el = $('#imageinput_' + this.elementId); + this.crossEl = $('#cross_' + this.elementId); + this.inputEl = $('#input_' + this.elementId); this.el.on('click', function (event) { _this.clickHandler(event); @@ -35,14 +38,10 @@ window.ImageInput = (function ($, undefined) { } function clickHandler(event) { - var iiDiv = document.getElementById('imageinput_' + this.elementId), - - posX = event.offsetX ? - event.offsetX : event.pageX - iiDiv.offsetLeft, + var posX = event.offsetX ? + event.offsetX : event.pageX - this.el[0].offsetLeft, posY = event.offsetY ? - event.offsetY : event.pageY - iiDiv.offsetTop, - - cross = document.getElementById('cross_' + this.elementId), + event.offsetY : event.pageY - this.el[0].offsetTop, // To reduce differences between values returned by different kinds // of browsers, we round `posX` and `posY`. @@ -51,12 +50,12 @@ window.ImageInput = (function ($, undefined) { // Chrome, FF: `posX` and `posY` - integers. result = '[' + Math.round(posX) + ',' + Math.round(posY) + ']'; - console.log('[image_input_click]: event = ', event); - - cross.style.left = (posX - 15) + 'px'; - cross.style.top = (posY - 15) + 'px'; - cross.style.visibility = 'visible'; + this.crossEl.css({ + left: posX - 15, + top: posY - 15, + visibility: 'visible' + }); - document.getElementById('input_' + this.elementId).value = result; + this.inputEl.val(result); } }).call(this, window.jQuery); From f03f3ebf96837a6f9903439126561b77514e4633 Mon Sep 17 00:00:00 2001 From: Valera Rozuvan Date: Mon, 16 Dec 2013 12:32:51 +0200 Subject: [PATCH 3/3] Adding Jasmine tests for ImageInput. --- .../xmodule/js/fixtures/imageinput.html | 31 ++++ .../xmodule/js/spec/capa/imageinput_spec.js | 145 ++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 common/lib/xmodule/xmodule/js/fixtures/imageinput.html create mode 100644 common/lib/xmodule/xmodule/js/spec/capa/imageinput_spec.js diff --git a/common/lib/xmodule/xmodule/js/fixtures/imageinput.html b/common/lib/xmodule/xmodule/js/fixtures/imageinput.html new file mode 100644 index 000000000000..00a109473354 --- /dev/null +++ b/common/lib/xmodule/xmodule/js/fixtures/imageinput.html @@ -0,0 +1,31 @@ + + + + + + + + + + + + Status: unanswered + + diff --git a/common/lib/xmodule/xmodule/js/spec/capa/imageinput_spec.js b/common/lib/xmodule/xmodule/js/spec/capa/imageinput_spec.js new file mode 100644 index 000000000000..d539108a88a9 --- /dev/null +++ b/common/lib/xmodule/xmodule/js/spec/capa/imageinput_spec.js @@ -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 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);