Browse Source

Improve form for create hotspot question - refs #7705

Angel Fernando Quiroz Campos 9 years ago
parent
commit
de63780e8d

+ 1 - 0
main/exercice/admin.php

@@ -344,6 +344,7 @@ $template = new Template();
 $htmlHeadXtra[] = $template->fetch('default/exercise/submit.js.tpl');
 $htmlHeadXtra[] = api_get_js('d3/jquery.xcolor.js');
 
+$htmlHeadXtra[] = '<link rel="stylesheet" href="../plugin/hotspot2/css/hotspot.css">';
 $htmlHeadXtra[] = '<script src="../plugin/hotspot2/js/hotspot.js"></script>';
 
 $htmlHeadXtra[] = "<script type=\"text/javascript\" src=\"../plugin/hotspot/JavaScriptFlashGateway.js\"></script>

+ 21 - 4
main/exercice/hotspot_actionscript_admin.as.php

@@ -22,10 +22,26 @@ $pictureWidth = $pictureSize[0];
 $pictureHeight = $pictureSize[1];
 
 $data = [];
-$data['hotspot_lang'] = $_course['language'];
-$data['hotspot_image'] = $pictureName;
-$data['hotspot_image_width'] = $pictureWidth;
-$data['hotspot_image_height'] = $pictureHeight;
+$data['lang'] = [
+    'Square' => get_lang('Square'),
+    'Circle' => get_lang('Circle'),
+    'Polygon' => get_lang('Polygon'),
+    'HotspotStatus1' => get_lang('HotspotStatus1'),
+    'HotspotStatus2Polygon' => get_lang('HotspotStatus2Polygon'),
+    'HotspotStatus2Other' => get_lang('HotspotStatus2Other'),
+    'HotspotStatus3' => get_lang('HotspotStatus3'),
+    'HotspotShowUserPoints' => get_lang('HotspotShowUserPoints'),
+    'ShowHotspots' => get_lang('ShowHotspots'),
+    'Triesleft' => get_lang('Triesleft'),
+    'HotspotExerciseFinished' => get_lang('HotspotExerciseFinished'),
+    'NextAnswer' => get_lang('NextAnswer'),
+    'Delineation' => get_lang('Delineation'),
+    'CloseDelineation' => get_lang('CloseDelineation'),
+    'Oar' => get_lang('oar')
+];
+$data['image'] = $objQuestion->selectPicturePath();
+$data['image_width'] = $pictureWidth;
+$data['image_height'] = $pictureHeight;
 $data['courseCode'] = $_course['path'];
 $data['hotspots'] = [];
 
@@ -39,6 +55,7 @@ $nbrAnswers = count($answers['answer']);
 
 for ($i=1;$i <= $nbrAnswers; $i++) {
     $hotSpot = [];
+    $hotSpot['id'] = null;
     $hotSpot['answer']= $answers['answer'][$i];
 
     if ($answer_type == HOT_SPOT_DELINEATION) {

+ 4 - 7
main/exercice/hotspot_admin.inc.php

@@ -1008,9 +1008,6 @@ if ($modifyAnswers) {
                         </tbody>
                     </table>
         </div>
-        <div class="row">
-            <div class="col-xs-12" id="hotspot-selectors"></div>
-        </div>
         <div class="row">
             <div class="col-xs-12">
     <?php
@@ -1018,16 +1015,16 @@ if ($modifyAnswers) {
     $height = 450;
     ?>
                 <div id="hotspot-container" class="center-block">
-                    <div id="hotspot-alert"></div>
-                    <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 0 0">
-                    </svg>
                 </div>
             </div>
         </div>
     </form>
     <script>
         $(document).on('ready', function () {
-            HotSpotAdmin.init(<?php echo $modifyAnswers ?>, '<?php echo $objQuestion->selectPicturePath(); ?>');
+            HotSpotAdmin.init({
+                questionId: <?php echo $modifyAnswers ?>,
+                selector: '#hotspot-container'
+            });
         });
     </script>
     <?php

+ 184 - 10
main/plugin/hotspot2/css/hotspot.css

@@ -1,15 +1,189 @@
 #hotspot-container {
-    line-height: 0;
-    /*position: relative;
-    width: 100%;
-    vertical-align: middle;
-    padding-bottom: 408px;
-    margin: 0;
-    overflow: hidden;*/
+    position: relative;
 }
 #hotspot-container svg {
-    /*display: inline-block;
+    
+}
+#hotspot-container #hotspot-context-menu {
     position: absolute;
-    top: 0;
-    left: 0;*/
+}
+
+.input-group.hotspot-0 .fa-square {
+   color: rgb(66, 113, 181);
+}
+.input-group.hotspot-1 .fa-square {
+   color: rgb(254, 142, 22);
+}
+.input-group.hotspot-2 .fa-square {
+   color: rgb(69, 199, 240);
+}
+.input-group.hotspot-3 .fa-square {
+   color: rgb(188, 214, 49);
+}
+.input-group.hotspot-4 .fa-square {
+   color: rgb(214, 49, 115);
+}
+.input-group.hotspot-5 .fa-square {
+   color: rgb(215, 215, 215);
+}
+.input-group.hotspot-6 .fa-square {
+   color: rgb(144, 175, 221);
+}
+.input-group.hotspot-7 .fa-square {
+   color: rgb(175, 134, 64);
+}
+.input-group.hotspot-8 .fa-square {
+   color: rgb(79, 146, 66);
+}
+.input-group.hotspot-9 .fa-square {
+   color: rgb(244, 235, 36);
+}
+.input-group.hotspot-10 .fa-square {
+   color: rgb(237, 32, 36);
+}
+.input-group.hotspot-11 .fa-square {
+   color: rgb(59, 59, 59);
+}
+
+.input-group.hotspot-0.active {
+    border-color: rgb(66, 113, 181);
+    box-shadow: 1px 1px 10px rgb(66, 113, 181);
+    border-radius: 4px;
+}
+.input-group.hotspot-0.active .input-group-addon {
+    border-color: rgb(66, 113, 181);
+}
+.input-group.hotspot-0.active select {
+    border-color: rgb(66, 113, 181);
+    border-left-color: #CCC;
+}
+.input-group.hotspot-1.active {
+    border-color: rgb(254, 142, 22);
+    box-shadow: 1px 1px 10px rgb(254, 142, 22);
+    border-radius: 4px;
+}
+.input-group.hotspot-1.active .input-group-addon {
+    border-color: rgb(254, 142, 22);
+}
+.input-group.hotspot-1.active select {
+    border-color: rgb(254, 142, 22);
+    border-left-color: #CCC;
+}
+.input-group.hotspot-2.active {
+    border-color: rgb(69, 199, 240);
+    box-shadow: 1px 1px 10px rgb(69, 199, 240);
+    border-radius: 4px;
+}
+.input-group.hotspot-2.active .input-group-addon {
+    border-color: rgb(69, 199, 240);
+}
+.input-group.hotspot-2.active select {
+    border-color: rgb(69, 199, 240);
+    border-left-color: #CCC;
+}
+.input-group.hotspot-3.active {
+    border-color: rgb(188, 214, 49);
+    box-shadow: 1px 1px 10px rgb(188, 214, 49);
+    border-radius: 4px;
+}
+.input-group.hotspot-3.active .input-group-addon {
+    border-color: rgb(188, 214, 49);
+}
+.input-group.hotspot-3.active select {
+    border-color: rgb(188, 214, 49);
+    border-left-color: #CCC;
+}
+
+.input-group.hotspot-0 select:focus,
+.input-group.hotspot-1 select:focus,
+.input-group.hotspot-2 select:focus,
+.input-group.hotspot-3 select:focus,
+.input-group.hotspot-4 select:focus,
+.input-group.hotspot-5 select:focus,
+.input-group.hotspot-6 select:focus,
+.input-group.hotspot-7 select:focus,
+.input-group.hotspot-8 select:focus,
+.input-group.hotspot-9 select:focus,
+.input-group.hotspot-10 select:focus,
+.input-group.hotspot-11 select:focus {
+    box-shadow: none;
+}
+.input-group.hotspot-0 select,
+.input-group.hotspot-1 select,
+.input-group.hotspot-2 select,
+.input-group.hotspot-3 select,
+.input-group.hotspot-4 select,
+.input-group.hotspot-5 select,
+.input-group.hotspot-6 select,
+.input-group.hotspot-7 select,
+.input-group.hotspot-8 select,
+.input-group.hotspot-9 select,
+.input-group.hotspot-10 select,
+.input-group.hotspot-11 select {
+    transition: none;
+    border-color: #CCC;
+}
+
+svg .hotspot-0,
+svg .hotspot-1,
+svg .hotspot-2,
+svg .hotspot-3,
+svg .hotspot-4,
+svg .hotspot-5,
+svg .hotspot-6,
+svg .hotspot-7,
+svg .hotspot-8,
+svg .hotspot-9,
+svg .hotspot-10,
+svg .hotspot-11 {
+    stroke-width: 2;
+}
+
+svg .hotspot-0 {
+    stroke: rgb(66, 113, 181);
+    fill: rgba(66, 113, 181, 0.75);
+}
+svg .hotspot-1 {
+    stroke: rgb(254, 142, 22);
+    fill: rgba(254, 142, 22, 0.75);
+}
+svg .hotspot-2 {
+    stroke: rgb(69, 199, 240);
+    fill: rgba(69, 199, 240, 0.75);
+}
+svg .hotspot-3 {
+    stroke: rgb(188, 214, 49);
+    fill: rgba(188, 214, 49, 0.75);
+}
+svg .hotspot-4 {
+    stroke: rgb(214, 49, 115);
+    fill: rgba(214, 49, 115, 0.75);
+}
+svg .hotspot-5 {
+    stroke: rgb(215, 215, 21);
+    fill: rgba(215, 215, 21, 0.75);
+}
+svg .hotspot-6 {
+    stroke: rgb(144, 175, 221);
+    fill: rgba(144, 175, 221, 0.75);
+}
+svg .hotspot-7 {
+    stroke: rgb(175, 134, 64);
+    fill: rgba(175, 134, 64, 0.75);
+}
+svg .hotspot-8 {
+    stroke: rgb(79, 146, 66);
+    fill: rgba(79, 146, 66, 0.75);
+}
+svg .hotspot-9 {
+    stroke: rgb(244, 235, 36);
+    fill: rgba(244, 235, 36, 0.75);
+}
+svg .hotspot-10 {
+    stroke: rgb(237, 32, 36);
+    fill: rgba(237, 32, 36, 0.75);
+}
+svg .hotspot-11 {
+    stroke: rgb(59, 59, 59);
+    fill: rgba(59, 59, 59, 0.75);
 }

+ 642 - 387
main/plugin/hotspot2/js/hotspot.js

@@ -1,505 +1,760 @@
 var HotSpotAdmin = (function () {
-    var HotSpotSquare = function () {
-        this.x = 0;
-        this.y = 0;
-        this.width = 0;
-        this.height = 0;
+    var HotspotModel = function (attributes) {
+        this.attributes = attributes;
+        this.id = 0;
+        this.name = '';
+
+        this.changeEvent = null;
+    };
+    HotspotModel.prototype.set = function (key, value) {
+        this.attributes[key] = value;
+
+        if (this.changeEvent) {
+            this.changeEvent(this);
+        }
+    };
+    HotspotModel.prototype.get = function (key) {
+        return this.attributes[key];
+    };
+    HotspotModel.prototype.onChange = function (callback) {
+        this.changeEvent = callback;
+    };
+    HotspotModel.prototype.checkPoint = function (x, y) {
+        return false;
     };
-    HotSpotSquare.prototype.setStartPoint = function (x, y) {
-        this.x = parseInt(x);
-        this.y = parseInt(y);
+    HotspotModel.decode = function () {
+        return new this;
+    };
+    HotspotModel.prototype.encode = function () {
+        return '';
     };
-    HotSpotSquare.prototype.setEndPoint = function (x, y) {
-        var x2, y2;
 
+    var SquareModel = function (attributes) {
+        HotspotModel.call(this, attributes);
+    };
+    SquareModel.prototype = Object.create(HotspotModel.prototype);
+    SquareModel.prototype.setStartPoint = function (x, y) {
         x = parseInt(x);
         y = parseInt(y);
 
-        if (x < this.x) {
-            x2 = this.x;
-            this.x = x;
-        } else {
-            x2 = x;
+        this.set('x', x);
+        this.set('y', y);
+    };
+    SquareModel.prototype.setEndPoint = function (x, y) {
+        var startX = this.get('x'),
+            startY = this.get('y');
+
+        x = parseInt(x);
+        y = parseInt(y);
+
+        if (x >= startX) {
+            this.set('width', x - startX);
         }
 
-        if (y < this.y) {
-            y2 = this.y;
-            this.y = y;
-        } else {
-            y2 = y;
+        if (y >= startY) {
+            this.set('height', y - startY);
         }
+    };
+    SquareModel.prototype.checkPoint = function (x, y) {
+        var left = this.get('x'),
+            right = this.get('x') + this.get('width'),
+            top = this.get('y'),
+            bottom = this.get('y') + this.get('height');
+
+        var xIsValid = x >= left && x <= right,
+            yIsValid = y >= top && y <= bottom;
 
-        this.width = Math.round(x2 - this.x);
-        this.height = Math.round(y2 - this.y);
+        return xIsValid && yIsValid;
     };
-    HotSpotSquare.prototype.encode = function () {
-        var encodedPosition = [this.x, this.y].join(';');
+    SquareModel.decode = function (hotspotInfo) {
+        var coords = hotspotInfo.coord.split('|'),
+            position = coords[0].split(';'),
+            hotspot = new SquareModel({
+                x: parseInt(position[0]),
+                y: parseInt(position[1]),
+                width: parseInt(coords[1]),
+                height: parseInt(coords[2])
+            });
+
+        hotspot.id = hotspotInfo.id;
+        hotspot.name = hotspotInfo.answer;
 
+        return hotspot;
+    };
+    SquareModel.prototype.encode = function () {
         return [
-            encodedPosition,
-            this.width,
-            this.height
+            [
+                this.get('x'),
+                this.get('y')
+            ].join(';'),
+            this.get('width'),
+            this.get('height')
         ].join('|');
-    }
+    };
 
-    var HotSpotEllipse = function () {
-        this.centerX = 0;
-        this.centerY = 0;
-        this.radiusX = 0;
-        this.radiusY = 0;
+    var EllipseModel = function (attributes) {
+        HotspotModel.call(this, attributes);
     };
-    HotSpotEllipse.prototype.setStartPoint = function (x, y) {
-        this.centerX = parseInt(x);
-        this.centerY = parseInt(y);
+    EllipseModel.prototype = Object.create(HotspotModel.prototype);
+    EllipseModel.prototype.setStartPoint = function (x, y) {
+        x = parseInt(x);
+        y = parseInt(y);
+
+        this.set('centerX', x);
+        this.set('centerY', y);
     };
-    HotSpotEllipse.prototype.setEndPoint = function (x, y) {
-        var startX = this.centerX,
-            startY = this.centerY,
-            endX = 0,
-            endY = 0;
+    EllipseModel.prototype.setEndPoint = function (x, y) {
+        var startX = this.get('centerX'),
+            startY = this.get('centerY'),
+            width = 0,
+            height = 0;
 
         x = parseInt(x);
         y = parseInt(y);
 
-        if (x < startX) {
-            endX = startX;
-            startX = x;
-        } else {
-            endX = x;
+        if (x >= startX) {
+            width = x - startX;
+
+            this.set('radiusX', Math.round(width / 2));
+            this.set('centerX', startX + this.get('radiusX'));
         }
 
-        if (y < startY) {
-            endY = startY;
-            startY = y;
-        } else {
-            endY = y;
+        if (y >= startY) {
+            height = y - startY;
+
+            this.set('radiusY', Math.round(height / 2));
+            this.set('centerY', startY + this.get('radiusY'));
         }
+    };
+    EllipseModel.prototype.checkPoint = function (x, y) {
+        var dX = x - this.get('centerX'),
+            dY = y - this.get('centerY');
 
-        var width = Math.round(endX - startX);
-        var height = Math.round(endY - startY);
+        var dividend = Math.pow(dX, 2) / Math.pow(this.get('radiusX'), 2),
+            divider = Math.pow(dY, 2) / Math.pow(this.get('radiusY'), 2);
 
-        this.radiusX = Math.round(width / 2);
-        this.radiusY = Math.round(height / 2);
-        this.centerX = startX + this.radiusX;
-        this.centerY = startY + this.radiusY;
+        return dividend + divider <= 1;
     };
-    HotSpotEllipse.prototype.encode = function () {
-        var encodedCenter = [this.centerX, this.centerY].join(';');
+    EllipseModel.decode = function (hotspotInfo) {
+        var coords = hotspotInfo.coord.split('|'),
+            center = coords[0].split(';'),
+            hotspot = new EllipseModel({
+                centerX: parseInt(center[0]),
+                centerY: parseInt(center[1]),
+                radiusX: parseInt(coords[1]),
+                radiusY: parseInt(coords[2])
+            });
+
+        hotspot.id = hotspotInfo.id;
+        hotspot.name = hotspotInfo.answer;
 
+        return hotspot;
+    };
+    EllipseModel.prototype.encode = function () {
         return [
-            encodedCenter,
-            this.radiusX,
-            this.radiusY
+            [
+                this.get('centerX'),
+                this.get('centerY')
+            ].join(';'),
+            this.get('radiusX'),
+            this.get('radiusY')
         ].join('|');
     };
 
-    var HotSpotPolygon = function () {
-        this.points = [];
-    };
-    HotSpotPolygon.prototype.addPoint = function (x, y) {
-        this.points.push([
-            parseInt(x),
-            parseInt(y)
-        ]);
+    var PolygonModel = function (attributes) {
+        HotspotModel.call(this, attributes);
     };
-    HotSpotPolygon.prototype.encode = function () {
-        var encodedPoints = [];
+    PolygonModel.prototype = Object.create(HotspotModel.prototype);
+    PolygonModel.prototype.addPoint = function (x, y) {
+        var points = this.get('points');
 
-        this.points.forEach(function (point) {
-            encodedPoints.push(point.join(';'));
-        });
+        x = parseInt(x);
+        y = parseInt(y);
 
-        return encodedPoints.join('|');
-    };
+        points.push([x, y]);
 
-    var HotSpotEl = function (hotspot, color) {
-        this.hotspot = hotspot;
-        this.element = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
-        this.elStroke = 'rgb(' + color + ')';
-        this.elFill = 'rgba(' + color + ', 0.5)';
+        this.set('points', points);
     };
-    HotSpotEl.prototype.render = function () {
-        return this;
-    };
-    HotSpotEl.prototype.remove = function () {
-        if (!this.element) {
-            return;
+    PolygonModel.prototype.checkPoint = function (x, y) {
+        var points = this.get('points'),
+            isInside = false;
+
+        for (var i = 0, j = points.length - 1; i < points.length; j = i++) {
+            var xi = points[i][0],
+                yi = points[i][1],
+                xj = points[j][0],
+                yj = points[j][1];
+
+            var intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
+
+            if (intersect) {
+                isInside = !isInside;
+            }
         }
 
-        this.element.parentNode.removeChild(this.element);
-        this.hotspot = null;
+        return isInside;
     };
+    PolygonModel.decode = function (hotspotInfo) {
+        var pairedPoints = hotspotInfo.coord.split('|'),
+            points = [],
+            hotspot = new PolygonModel({
+                points: []
+            });
 
-    var SquareEl = function (hotspot, color) {
-        HotSpotEl.call(this, hotspot, color);
+        $.each(pairedPoints, function (index, pair) {
+            var point = pair.split(';');
 
-        this.element = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
-        this.element.setAttribute('stroke-width', 2);
-        this.element.setAttribute('stroke', this.elStroke);
-        this.element.setAttribute('fill', this.elFill);
-    };
-    SquareEl.prototype = Object.create(HotSpotEl.prototype);
-    SquareEl.prototype.render = function () {
-        this.element.setAttribute('x', this.hotspot.x);
-        this.element.setAttribute('y', this.hotspot.y);
-        this.element.setAttribute('width', this.hotspot.width);
-        this.element.setAttribute('height', this.hotspot.height);
+            points.push([
+                point[0],
+                point[1]
+            ]);
+        });
 
-        return this.element;
+        hotspot.set('points', points);
+
+        return hotspot;
     };
+    PolygonModel.prototype.encode = function () {
+        var pairedPoints = [];
 
-    var EllipseEl = function (hotspot, color) {
-        HotSpotEl.call(this, hotspot, color);
+        this.get('points').forEach(function (point) {
+            pairedPoints.push(
+                point.join(';')
+            );
+        });
 
-        this.element = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse');
-        this.element.setAttribute('stroke-width', 2);
-        this.element.setAttribute('stroke', this.elStroke);
-        this.element.setAttribute('fill', this.elFill);
+        return pairedPoints.join('|');
     };
-    EllipseEl.prototype = Object.create(HotSpotEl.prototype);
-    EllipseEl.prototype.render = function () {
-        this.element.setAttribute('cx', this.hotspot.centerX);
-        this.element.setAttribute('cy', this.hotspot.centerY);
-        this.element.setAttribute('rx', this.hotspot.radiusX);
-        this.element.setAttribute('ry', this.hotspot.radiusY);
 
-        return this.element;
-    };
+    var HotspotsCollection = function () {
+        this.hotspots = [];
+        this.length = 0;
 
-    var PolygonEl = function (hotspot, color) {
-        HotSpotEl.call(this, hotspot, color);
+        this.addEvent = null;
+    };
+    HotspotsCollection.prototype.add = function (hotspot) {
+        this.hotspots.push(hotspot);
+        this.length++;
 
-        this.element = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
-        this.element.setAttribute('stroke-width', 2);
-        this.element.setAttribute('stroke', this.elStroke);
-        this.element.setAttribute('fill', this.elFill);
+        if (this.addEvent) {
+            this.addEvent(hotspot);
+        }
+    };
+    HotspotsCollection.prototype.get = function (index) {
+        return this.hotspots[index];
+    };
+    HotspotsCollection.prototype.set = function (index, newHotspot) {
+        this.hotspots[index] = newHotspot;
+    };
+    HotspotsCollection.prototype.onAdd = function (callback) {
+        this.addEvent = callback;
     };
-    PolygonEl.prototype = Object.create(HotSpotEl.prototype);
-    PolygonEl.prototype.render = function () {
-        var pointsPaired = [];
 
-        this.hotspot.points.forEach(function (point) {
-            pointsPaired.push(point.join(','));
-        });
+    var HotspotSVG = function (modelModel, index) {
+        var self = this;
 
-        this.element.setAttribute(
-            'points',
-            pointsPaired.join(' ')
-        );
+        this.model = modelModel;
+        this.hotspotIndex = index;
 
-        return this.element;
-    };
+        this.el = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
 
-    var HotSpotSelectorEl = function (color, index, selectedValue) {
-        this.hotSpotIndex = parseInt(index);
-        this.elStroke = 'rgb(' + color + ')';
-        this.elFill = 'rgba(' + color + ', 0.5)';
+        this.model.onChange(function (hotspotModel) {
+            self.render();
+        });
+    };
+    HotspotSVG.prototype.render = function () {
+        var newEl = null;
+
+        if (this.model instanceof SquareModel) {
+            newEl = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
+            newEl.setAttribute('x', this.model.get('x'));
+            newEl.setAttribute('y', this.model.get('y'));
+            newEl.setAttribute('width', this.model.get('width'));
+            newEl.setAttribute('height', this.model.get('height'));
+        } else if (this.model instanceof EllipseModel) {
+            newEl = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse');
+            newEl.setAttribute('cx', this.model.get('centerX'));
+            newEl.setAttribute('cy', this.model.get('centerY'));
+            newEl.setAttribute('rx', this.model.get('radiusX'));
+            newEl.setAttribute('ry', this.model.get('radiusY'));
+        } else if (this.model instanceof PolygonModel) {
+            var pointsPaired = [];
+
+            this.model.get('points').forEach(function (point) {
+                pointsPaired.push(point.join(','));
+            });
 
-        switch (selectedValue) {
-            case 'square':
-            default:
-                this.selectedValue = 'square';
-                break;
+            newEl = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
+            newEl.setAttribute(
+                'points',
+                pointsPaired.join(' ')
+            );
+        }
 
-            case 'circle':
-                this.selectedValue = 'ellipse';
-                break;
+        newEl.setAttribute('class', 'hotspot-' + this.hotspotIndex);
 
-            case 'poly':
-                this.selectedValue = 'polygon';
-                break;
+        if (this.el.parentNode) {
+            this.el.parentNode.replaceChild(newEl, this.el);
         }
+
+        this.el = newEl;
+
+        return this;
     };
-    HotSpotSelectorEl.prototype.render = function () {
-        var self = this;
+
+    var HotspotSelect = function (index, hotspotsCollection, hotspotSVG) {
+        this.hotspotIndex = index;
+        this.hotspotsCollection = hotspotsCollection;
+        this.hotspotSVG = hotspotSVG;
 
         this.el = document.createElement('div');
-        this.el.className = 'col-xs-6 col-sm-3 col-md-2';
-        this.el.innerHTML = '\n\
-            <div class="input-group">\n\
-                <span class="input-group-addon" id="hotspot-' + this.hotSpotIndex + '">\n\
-                    <span class="fa fa-square fa-fw" data-hidden="true" style="color: ' + this.elStroke + '"></span>\n\
+        this.el.className = 'col-xs-6 col-sm-4 col-md-3 col-lg-2';
+
+        selectedHotspotIndex = this.hotspotIndex;
+
+        $('.input-group').removeClass('active');
+    };
+    HotspotSelect.prototype.render = function () {
+        var self = this,
+            $el = $(this.el);
+
+        var template = '\n\
+            <div class="input-group hotspot-'  + this.hotspotIndex + ' active">\n\
+                <span class="input-group-addon" id="hotspot-' + this.hotspotIndex + '">\n\
+                    <span class="fa fa-square fa-fw" data-hidden="true"></span>\n\
+                    <span class="sr-only">' + (this.hotspotSVG.model.get('name') ? this.hotspotSVG.model.get('name') : 'hotspot ' + this.hotspotIndex) + '</span>\n\
                 </span>\n\
-                <select class="form-control" aria-describedby="hotspot-' + this.hotSpotIndex + '">\n\
-                    <option value="">Select</option>\n\
-                    <option value="square">Square</option>\n\
-                    <option value="ellipse">Ellipse</option>\n\
-                    <option value="polygon">Polygon</option>\n\
+                <select class="form-control" aria-describedby="hotspot-' + this.hotspotIndex + '">\n\
+                    <option value="square">' + lang.Square + '</option>\n\
+                    <option value="ellipse">' + lang.Circle + '</option>\n\
+                    <option value="polygon">' + lang.Polygon + '</option>\n\
                 </select>\n\
             </div>\n\
         ';
+        $el.html(template);
+
+        $el.find('select')
+            .on('change', function () {
+                selectedHotspotIndex = self.hotspotIndex;
+
+                var newHotspot = null,
+                    changeEvent = self.hotspotSVG.model.changeEvent;
+
+                switch (this.value) {
+                    case 'square':
+                        newHotspot = new SquareModel({
+                            x: 0,
+                            y: 0,
+                            width: 0,
+                            height: 0
+                        });
+                        break;
+
+                    case 'ellipse':
+                        newHotspot = new EllipseModel({
+                            centerX: 0,
+                            centerY: 0,
+                            radiusX: 0,
+                            radiusY: 0
+                        });
+                        break;
+
+                    case 'polygon':
+                        newHotspot = new PolygonModel({
+                            points: []
+                        });
+                        break;
+                }
 
-        $(this.el).find('select').val(this.selectedValue);
-
-        var selectShapeEvent = function (e) {
-            switch (this.value) {
-                case 'square':
-                    //no break
-                case 'ellipse':
-                    //no break
-                case 'polygon':
-                    currentShapeType = this.value;
-                    currentHotSpotIndex = self.hotSpotIndex;
-                    break;
-
-                default:
-                    break;
-            }
-        };
+                newHotspot.onChange(changeEvent);
 
-        $(this.el).on('click', selectShapeEvent);
-        $(this.el).find('select').on('change', selectShapeEvent);
-
-        return this.el;
-    };
-
-    var colors = [
-        '66, 113, 181',
-        '254, 142, 22',
-        '69, 199, 240',
-        '188, 214, 49',
-        '214, 49, 115',
-        '215, 215, 215',
-        '144, 175, 221',
-        '174, 134, 64',
-        '79, 146, 66',
-        '244, 235, 36',
-        '237, 32, 36',
-        '59, 59, 59',
-        '247, 189, 226'
-    ];
-
-    var getPointOnImage = function (x, y) {
-        var pointerPosition = {
-            left: x + window.scrollX,
-            top: y + window.scrollY
-        },
-            canvasOffset = {
-                x: canvas.getBoundingClientRect().x + window.scrollX,
-                y: canvas.getBoundingClientRect().y + window.scrollY
-            };
+                self.hotspotsCollection.set(self.hotspotIndex, newHotspot);
+                self.hotspotSVG.model = newHotspot;
+            })
+            .on('focus', function () {
+                $('.input-group').removeClass('active');
 
-        return {
-            x: Math.round(pointerPosition.left - canvasOffset.x),
-            y: Math.round(pointerPosition.top - canvasOffset.y)
-        };
+                $el.find('.input-group').addClass('active');
+
+                selectedHotspotIndex = self.hotspotIndex;
+            })
+            .val(function () {
+                if (self.hotspotSVG.model instanceof SquareModel) {
+                    return 'square';
+                }
+
+                if (self.hotspotSVG.model instanceof EllipseModel) {
+                    return 'ellipse';
+                }
+
+                if (self.hotspotSVG.model instanceof PolygonModel) {
+                    return 'polygon';
+                }
+            });
+
+        return this;
     };
 
-    var startCanvas = function () {
-        var newHotSpotEl = null,
-            pressingShift = false;
+    var ContextMenu = function () {
+        this.el = document.createElement('ul');
 
-        document.addEventListener('keydown', function (e) {
-            if (e.keyCode === 16) {
-                pressingShift = true;
-            }
-        }, false);
-        document.addEventListener('keyup', function (e) {
-            if (e.keyCode === 16) {
-                pressingShift = false;
-            }
-        }, false);
+        $(this.el).addClass('dropdown-menu').attr('id', "hotspot-context-menu");
 
-        canvas.addEventListener('click', function (e) {
+        this.hideEvent = null;
+    };
+    ContextMenu.prototype.onHide = function (callback) {
+        this.hideEvent = callback;
+    };
+    ContextMenu.prototype.render = function () {
+        var self = this,
+            template = '\n\
+                <li>\n\
+                    <a href="#">' + 'ClosePolygon' + '</a>\n\
+                </li>\n\
+            ';
+
+        $(this.el).html(template);
+        
+        $(this.el).find('a').on('click', function (e) {
             e.preventDefault();
-        }, false);
 
-        container.addEventListener('dragstart', function (e) {
-            e.preventDefault();
-        }, false);
-        container.addEventListener('click', function (e) {
-            if (shapes.length >= colors.length) {
-                return;
+            if (self.hideEvent) {
+                self.hideEvent(e);
             }
 
-            newHotSpotEl = draw(newHotSpotEl, e.clientX, e.clientY, pressingShift);
+            $(self.el).hide();
+        });
 
-            if (!newHotSpotEl) {
-                updateValues();
-            }
-        }, false);
+        return this;
+    };
+    ContextMenu.prototype.show = function (x, y) {
+        $(this.el).css({left: x, top: y}).show();
+    };
+
+    var HotspotsSVG = function (hotspotsCollection, image) {
+        var self = this;
+
+        this.el = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
+        this.collection = hotspotsCollection;
+        this.image = image;
+
+        this.collection.onAdd(function (hotspotModel) {
+            self.renderHotspot(hotspotModel);
+        });
     };
+    HotspotsSVG.prototype.render = function () {
+        this.el.setAttribute('version', '1.1');
+        this.el.setAttribute('viewBox', '0 0 ' + this.image.width + ' ' + this.image.height);
 
-    var draw = function (hotSpotEl, x, y, isPressingShift) {
-        var pointerPosition = getPointOnImage(x, y),
-            hotSpot = null;
+        var imageSvg = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+        imageSvg.setAttributeNS('http://www.w3.org/1999/xlink', 'href', this.image.src);
+        imageSvg.setAttribute('width', this.image.width);
+        imageSvg.setAttribute('height', this.image.height);
 
-        if (!hotSpotEl) {
-            switch (currentShapeType) {
-                case 'square':
-                    //no break
-                case 'ellipse':
-                    if (currentShapeType === 'ellipse') {
-                        hotSpot = new HotSpotEllipse();
-                        hotSpotEl = new EllipseEl(hotSpot, colors[currentHotSpotIndex]);
+        this.el.appendChild(imageSvg);
+
+        this.setEvents();
+
+        return this;
+    };
+    HotspotsSVG.prototype.renderHotspot = function (hotspot) {
+        var hotspotIndex = this.collection.length - 1,
+            hotspotSVG = new HotspotSVG(hotspot, hotspotIndex);
+
+        this.el.appendChild(
+            hotspotSVG.render().el
+        );
+
+        var hotspotSelect = new HotspotSelect(hotspotIndex, this.collection, hotspotSVG);
+
+        $(config.selector).parent().find('.row').append(
+            hotspotSelect.render().el
+        );
+    };
+    HotspotsSVG.prototype.setEvents = function () {
+        var self = this,
+            $el = $(this.el);
+            isDrawing = false;
+
+        var getPointOnImage = function (x, y) {
+                var pointerPosition = {
+                        left: x + window.scrollX,
+                        top: y + window.scrollY
+                    },
+                    canvasOffset = {
+                        x: self.el.getBoundingClientRect().left + window.scrollX,
+                        y: self.el.getBoundingClientRect().top + window.scrollY
+                    };
+
+                return {
+                    x: Math.round(pointerPosition.left - canvasOffset.x),
+                    y: Math.round(pointerPosition.top - canvasOffset.y)
+                };
+            },
+            startPoint = {
+                x: 0,
+                y: 0
+            };
+
+        $el.on('dragstart', function (e) {
+                e.preventDefault();
+            })
+            .on('mousedown', function (e) {
+                e.preventDefault();
+
+                if (e.button > 0) {
+                    return;
+                }
+
+                if (self.collection.length <= 0) {
+                    return;
+                }
+
+                var currentHotspot = self.collection.get(selectedHotspotIndex);
+
+                if (!currentHotspot) {
+                    return;
+                }
+
+                startPoint = getPointOnImage(e.clientX, e.clientY);
+
+                if (currentHotspot instanceof SquareModel) {
+                    isDrawing = true;
+
+                    currentHotspot.set('x', startPoint.x);
+                    currentHotspot.set('y', startPoint.y);
+                    currentHotspot.set('width', 0);
+                    currentHotspot.set('height', 0);
+
+                    return;
+                }
+
+                if (currentHotspot instanceof EllipseModel) {
+                    isDrawing = true;
+
+                    currentHotspot.set('centerX', 0);
+                    currentHotspot.set('centerY', 0);
+                    currentHotspot.set('radiusX', 0);
+                    currentHotspot.set('radiusY', 0);
+                    return;
+                }
+            })
+            .on('mousemove', function (e) {
+                e.preventDefault();
+
+                if (self.collection.length <= 0) {
+                    return;
+                }
+
+                if (!isDrawing) {
+                    return;
+                }
+
+                var currentHotspot = self.collection.get(selectedHotspotIndex),
+                    currentPoint = getPointOnImage(e.clientX, e.clientY);
+
+                if (!currentHotspot) {
+                    return;
+                }
+
+                if (currentHotspot instanceof SquareModel) {
+                    if (startPoint.x < currentPoint.x) {
+                        currentHotspot.set('width', currentPoint.x - startPoint.x);
                     } else {
-                        hotSpot = new HotSpotSquare();
-                        hotSpotEl = new SquareEl(hotSpot, colors[currentHotSpotIndex]);
+                        currentHotspot.set('x', currentPoint.x);
+                        currentHotspot.set('width', startPoint.x - currentPoint.x);
                     }
 
-                    hotSpot.setStartPoint(pointerPosition.x, pointerPosition.y);
-                    break;
+                    if (startPoint.y < currentPoint.y) {
+                        currentHotspot.set('height', currentPoint.y - startPoint.y);
+                    } else {
+                        currentHotspot.set('y', currentPoint.y);
+                        currentHotspot.set('height', startPoint.y - currentPoint.y);
+                    }
 
-                case 'polygon':
-                    hotSpot = new HotSpotPolygon();
-                    hotSpotEl = new PolygonEl(hotSpot, colors[currentHotSpotIndex]);
+                    return;
+                }
 
-                    hotSpot.addPoint(pointerPosition.x, pointerPosition.y);
-                    break;
-            }
+                if (currentHotspot instanceof EllipseModel) {
+                    var width = 0,
+                        height = 0;
 
-            shapes[currentHotSpotIndex].remove();
-            shapes.splice(currentHotSpotIndex, 1, hotSpotEl);
+                    if (startPoint.x < currentPoint.x) {
+                        width = currentPoint.x - startPoint.x;
 
-            canvas.appendChild(hotSpotEl.render());
+                        currentHotspot.set('radiusX', Math.round(width / 2));
+                        currentHotspot.set('centerX', startPoint.x + currentHotspot.get('radiusX'));
+                    } else {
+                        width = startPoint.x - currentPoint.x;
 
-            return hotSpotEl;
-        }
+                        currentHotspot.set('radiusX', Math.round(width / 2));
+                        currentHotspot.set('centerX', currentPoint.x + currentHotspot.get('radiusX'))
+                    }
 
-        switch (currentShapeType) {
-            case 'square':
-                //no break
-            case 'ellipse':
-                hotSpotEl.hotspot.setEndPoint(pointerPosition.x, pointerPosition.y);
-                hotSpotEl.render();
+                    if (startPoint.y < currentPoint.y) {
+                        height = currentPoint.y - startPoint.y;
 
-                hotSpotEl = null;
-                break;
+                        currentHotspot.set('radiusY', Math.round(height / 2));
+                        currentHotspot.set('centerY', startPoint.y + currentHotspot.get('radiusY'));
+                    } else {
+                        height = startPoint.y - currentPoint.y;
 
-            case 'polygon':
-                $(container).find('#hotspot-alert').text('Keed pressed the SHIFT key and click the image to close the polygon');
+                        currentHotspot.set('radiusY', Math.round(height / 2));
+                        currentHotspot.set('centerY', currentPoint.y + currentHotspot.get('radiusY'));
+                    }
 
-                hotSpotEl.hotspot.addPoint(pointerPosition.x, pointerPosition.y);
-                hotSpotEl.render();
+                    return;
+                }
+            })
+            .on('mouseup', function (e) {
+                e.preventDefault();
 
-                if (isPressingShift) {
-                    hotSpotEl = null;
-                    $(container).find('#hotspot-alert').text('');
+                if (e.button > 0) {
+                    return;
                 }
-                break;
-        }
 
-        return hotSpotEl;
-    };
+                if (self.collection.length <= 0) {
+                    return;
+                }
 
-    var updateValues = function () {
-        var currentHotSpotEl = shapes[currentHotSpotIndex];
+                if (!isDrawing) {
+                    return;
+                }
 
-        if (currentHotSpotIndex === undefined) {
-            return;
-        }
+                var currentHotspot = self.collection.get(selectedHotspotIndex),
+                    hotspotTypeSelector = '[name="hotspot_type[' + (selectedHotspotIndex + 1) + ']"]',
+                    hotspotCoordSelector = '[name="hotspot_coordinates[' + (selectedHotspotIndex + 1) + ']"]';
 
-        if (currentHotSpotEl.hotspot instanceof HotSpotSquare) {
-            $('[name="hotspot_type[' + (currentHotSpotIndex + 1) + ']"]').val('square');
-        } else if (currentHotSpotEl.hotspot instanceof HotSpotEllipse) {
-            $('[name="hotspot_type[' + (currentHotSpotIndex + 1) + ']"]').val('circle');
-        } else if (currentHotSpotEl.hotspot instanceof HotSpotPolygon) {
-            $('[name="hotspot_type[' + (currentHotSpotIndex + 1) + ']"]').val('poly');
-        }
+                if (!currentHotspot) {
+                    return;
+                }
 
-        $('[name="hotspot_coordinates[' + (currentHotSpotIndex + 1) + ']"]').val(
-            currentHotSpotEl.hotspot.encode()
-        );
-    };
+                if (currentHotspot instanceof SquareModel) {
+                    $(hotspotTypeSelector).val('square');
+                    $(hotspotCoordSelector).val(currentHotspot.encode());
 
-    var loadHotSpots = function (hotSpotList) {
-        hotSpotList.forEach(function (hotSpotData, index) {
-            var hotSpot = null,
-                hotSpotEl = null,
-                color = colors[shapes.length];
-
-            switch (hotSpotData.type) {
-                case 'square':
-                    hotSpot = new HotSpotSquare();
-                    hotSpotEl = new SquareEl(hotSpot, color);
-
-                    var coords = hotSpotData.coord.split('|'),
-                        position = coords[0].split(';'),
-                        x = parseInt(position[0]),
-                        y = parseInt(position[1]),
-                        width = parseInt(coords[1]),
-                        height = parseInt(coords[2]);
-
-                    hotSpot.setStartPoint(x, y);
-                    hotSpot.setEndPoint(x + width, y + height);
-                    break;
-                case 'circle':
-                    hotSpot = new HotSpotEllipse();
-                    hotSpotEl = new EllipseEl(hotSpot, color);
-
-                    var coords = hotSpotData.coord.split('|'),
-                        position = coords[0].split(';'),
-                        x = parseInt(position[0] - coords[1]),
-                        y = parseInt(position[1] - coords[2]),
-                        width = parseInt(coords[1]) * 2,
-                        height = parseInt(coords[2]) * 2;
-
-                    hotSpot.setStartPoint(x, y);
-                    hotSpot.setEndPoint(x + width, y + height);
-                    break;
-
-                case 'poly':
-                    hotSpot = new HotSpotPolygon();
-                    hotSpotEl = new PolygonEl(hotSpot, color);
-
-                    hotSpotData.coord.split('|').forEach(function (point) {
-                        var exis = point.split(';');
-
-                        hotSpot.addPoint(exis[0], exis[1]);
-                    });
-                    break;
-            }
+                    isDrawing = false;
+                } else if (currentHotspot instanceof EllipseModel) {
+                    $(hotspotTypeSelector).val('circle');
+                    $(hotspotCoordSelector).val(currentHotspot.encode());
 
-            if (hotSpotEl) {
-                var hotSpotSelector = new HotSpotSelectorEl(color, index, hotSpotData.type);
+                    isDrawing = false;
+                }
+            })
+            .on('click', function (e) {
+                e.preventDefault();
 
-                selectors.appendChild(hotSpotSelector.render());
-                canvas.appendChild(hotSpotEl.render());
-                shapes.push(hotSpotEl);
-            }
-        });
+                var currentHotspot = self.collection.get(selectedHotspotIndex),
+                    currentPoint = getPointOnImage(e.clientX, e.clientY);
+
+                if (!currentHotspot) {
+                    return;
+                }
+
+                if (currentHotspot instanceof PolygonModel) {
+                    var points = [];
+
+                    if (!isDrawing) {
+                        isDrawing = true;
+                    } else {
+                        points = currentHotspot.get('points');
+                    }
+
+                    points.push([currentPoint.x, currentPoint.y]);
+
+                    currentHotspot.set('points', points);
+
+                    return;
+                }
+            })
+            .on('contextmenu', function (e) {
+                e.preventDefault();
+
+                var currentPoint = getPointOnImage(e.clientX, e.clientY),
+                    currentHotspot = self.collection.get(selectedHotspotIndex),
+                    hotspotTypeSelector = '[name="hotspot_type[' + (selectedHotspotIndex + 1) + ']"]',
+                    hotspotCoordSelector = '[name="hotspot_coordinates[' + (selectedHotspotIndex + 1) + ']"]';
+
+                if (!currentHotspot) {
+                    return;
+                }
+
+                if (currentHotspot instanceof PolygonModel) {
+                    contextMenu.show(currentPoint.x, currentPoint.y);
+                    contextMenu.onHide(function () {
+                        $(hotspotTypeSelector).val('poly');
+                        $(hotspotCoordSelector).val(currentHotspot.encode());
+
+                        isDrawing = false;
+                    });
+                }
+            });
     };
 
-    var container, canvas, selectors, currentShapeType, currentHotSpotIndex, shapes = [];
+    var startHotspotsAdmin = function (questionInfo) {
+        var image = new Image();
+        image.onload = function () {
+            var hotspotsCollection = new HotspotsCollection();
 
-    return {
-        init: function (questionId, imageSrc) {
-            if (!questionId || !imageSrc) {
-                return;
-            }
+            var hotspotsSVG = new HotspotsSVG(hotspotsCollection, this);
 
-            selectors = document.querySelector('#hotspot-selectors');
-            container = document.querySelector('#hotspot-container');
-            canvas = document.querySelector('#hotspot-container svg');
-            currentShapeType = 'square';
+            $(config.selector)
+                .css('width', image.width)
+                .append(hotspotsSVG.render().el);
 
-            var xhrImage = new $.Deferred();
+            $(config.selector).parent().append('<div class="row"></div>');
 
-            var image = new Image();
-            image.onload = function () {
-                xhrImage.resolve(this);
-            };
-            image.onerror = function () {
-                xhrImage.reject();
-            };
-            image.src = imageSrc;
+            contextMenu = new ContextMenu();
+
+            $(config.selector).append(
+                contextMenu.render().el
+            );
+
+            $.each(questionInfo.hotspots, function (index, hotspotInfo) {
+                var hotspot = null;
+
+                switch (hotspotInfo.type) {
+                    case 'square':
+                    default:
+                        hotspot = SquareModel.decode(hotspotInfo);
+                        break;
 
-            var xhrHotSpots = $.get('/main/exercice/hotspot_actionscript_admin.as.php', {
-                modifyAnswers: parseInt(questionId)
+                    case 'circle':
+                        hotspot = EllipseModel.decode(hotspotInfo);
+                        break;
+
+                    case 'poly':
+                        hotspot = PolygonModel.decode(hotspotInfo);
+                        break;
+                }
+
+                hotspotsCollection.add(hotspot);
             });
+        };
+        image.src = questionInfo.image;
+
+        lang = questionInfo.lang;
+    };
 
-            $.when.apply($, [xhrImage, xhrHotSpots]).done(function (imageRequest, hotSpotsRequest) {
-                var imageSvg = document.createElementNS('http://www.w3.org/2000/svg', 'image');
-                imageSvg.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', imageRequest.src);
-                imageSvg.setAttribute('width', imageRequest.width);
-                imageSvg.setAttribute('height', imageRequest.height);
+    var config, lang, selectedHotspotIndex = 0, contextMenu;
 
-                container.style.width = imageRequest.width + 'px';
-                canvas.setAttribute('viewBox', '0 0 ' + imageRequest.width + ' ' + imageRequest.height);
-                canvas.appendChild(imageSvg);
+    return {
+        init: function (settings) {
+            config = $.extend({
+                questionId: 0,
+                selector: ''
+            }, settings);
+
+            if (!config.questionId || !config.selector) {
+                return;
+            }
+
+            var xhrQuestion = $.getJSON('/main/exercice/hotspot_actionscript_admin.as.php', {
+                modifyAnswers: parseInt(config.questionId)
+            });
 
-                loadHotSpots(hotSpotsRequest[0].hotspots);
-                startCanvas();
+            $.when(xhrQuestion).done(function (questionInfo) {
+                startHotspotsAdmin(questionInfo);
             });
         }
     };

+ 2 - 3
main/plugin/hotspot2/js/hotspot_user.js

@@ -96,8 +96,8 @@ var HotSpotUser = (function () {
                     top: y + window.scrollY
                 },
                 canvasOffset = {
-                    x: self.el.getBoundingClientRect().x + window.scrollX,
-                    y: self.el.getBoundingClientRect().y + window.scrollY
+                    x: self.el.getBoundingClientRect().left + window.scrollX,
+                    y: self.el.getBoundingClientRect().top + window.scrollY
                 };
 
             return {
@@ -126,7 +126,6 @@ var HotSpotUser = (function () {
             self.addAnswer(answer);
 
             if (answers.length === hotSpots.length) {
-                console.log(lang);
                 self.messagesEl.textContent = lang.HotspotExerciseFinished;
 
                 return;