closedquestion-8.x-3.x-dev/assets/js/closedquestion_arrow.js

assets/js/closedquestion_arrow.js
/**
 * Closed Question Chemical Reaction question
 * @license: GPL-2    See http://www.gnu.org/licenses/gpl-2.0.html
 * @author Koos van der Kolk / Kryt B.V. The Netherlands (www.kryt.nl)
 */
(function ($) {
  "use strict";

  /**
   * Add Drupal behavior: Connect the cqArrowQuestion to the
   * Drupal form element
   */
  Drupal.behaviors.closedQuestionArrow = {
    "attach": function (context) {
      var questionId;
      var settings = drupalSettings.closedQuestion.cr;

      for (questionId in settings) {
        /* create cqArrowQuestion objects */
        var $answerContainer = $("#" + questionId + "answerContainer");
        var $image_element = $answerContainer.find('img').first();
        var $answerFormElement = $('input[name=' + questionId + 'answer]');

        if ($image_element.data('cqArrowQuestion') === undefined) {
          /* init arrow question plugin */
          var cqArrowQuestionPromise = cqArrowQuestion($image_element, settings[questionId]);

          cqArrowQuestionPromise.then(function () {
            /* set current answer when plugin is inited */
            var cqArrowQuestion = $image_element.data('cqArrowQuestion');
            cqArrowQuestion.setAnswer($answerFormElement.val());

            /* create handler to connect plugin to closed question form */
            var onUpdateAnswer = function () {
              $answerFormElement.val(cqArrowQuestion.getAnswer());
            };
            /* let question tell us when user updates answer */
            cqArrowQuestion.registerPlugin("submitFeedback", {"hooks": {
                "onAddLineToAnswer": onUpdateAnswer,
                "onRemoveLineFromAnswer": onUpdateAnswer
              }});
          });
        }
      }
    }
  };

  /**
   * Turns an image into a arrow question
   * @param {object} element The image
   * @param {object} settings Settings (see defaultSettings declaration in code)
   * @returns {object} The question objects public properties
   */
  var cqArrowQuestion = function (element, settings) {
    /* define vars
     */

    /* this object will be exposed to other objects */
    var publicObj = {};

    //the version number of the plugin
    publicObj.version = '1.2'

    /* this object holds functions used by the plugin boilerplate */
    var _helper = {
      /**
       * Call hooks, additinal parameters will be passed on to registered plugins
       * @param {string} name
       */
      "doHook": function (name) {
        var i;
        var pluginFunctionArgs = [];

        /* call function */
        if (_globals.plugins !== undefined) {
          /* remove first two arguments */
          for (i = 1; i < arguments.length; i++) {
            pluginFunctionArgs.push(arguments[i]);
          }

          $.each(_globals.plugins, function (cqArrowQuestion, extPlugin) {
            if (extPlugin.__hooks !== undefined && extPlugin.__hooks[name] !== undefined) {
              extPlugin.__hooks[name].apply(publicObj, pluginFunctionArgs);
            }
          });
        }
      },
      /**
       * Registers a plugin
       * @param {string} name Name of plugin, must be unique
       * @param {object} object An object {("functions": {},) (, "hooks: {})}
       */
      "registerPlugin": function (name, object) {
        var plugin;
        var hooks;

        /* reorder plugin */
        hooks = $.extend(true, {}, object.hooks);
        plugin = object.functions !== undefined ? object.functions : {};
        plugin.__hooks = hooks;

        /* add plugin */
        _globals.plugins[name] = plugin;
      },
      /**
       * Calls a plugin function, all additional arguments will be passed on
       * @param {string} cqArrowQuestion
       * @param {string} pluginFunctionName
       */
      "callPluginFunction": function (cqArrowQuestion, pluginFunctionName) {
        var i;

        /* remove first two arguments */
        var pluginFunctionArgs = [];
        for (i = 2; i < arguments.length; i++) {
          pluginFunctionArgs.push(arguments[i]);
        }

        /* call function */
        _globals.plugins[cqArrowQuestion][pluginFunctionName].apply(null, pluginFunctionArgs);
      },
      /**
       * Checks dependencies based on the _globals.dependencies object
       * @returns {boolean}
       */
      "checkDependencies": function () {
        var dependenciesPresent = true;
        for (var libName in _globals.dependencies) {
          var callback = _globals.dependencies[libName];
          if (callback.call() === false) {
            console.error('jquery.cqArrowQuestion: Library ' + libName + ' not found! This may give unexpected results or errors.')
            dependenciesPresent = false;
          }
        }

        return dependenciesPresent;
      }
    };

    /* this object holds all global variables */
    var _globals = {};

    /* handle settings */
    _globals.settings = {};

    var defaultSettings = {
      "lineColor": "#dd4c00", /* line color */
      "lineSelectedColor": "#DBBA00", /* selection color */
      "showHotspots": true, /* whether to draw hotspots or not */
      "hotspotColor": "#ccc", /* hotspot border color */
      "showHotspotLabels": false, /* draw hotspot labels or not */
      "lineStyle": "straight",
      "endArrow": true,
      "startArrow": true,
      "lineNumbering": true,
      "hotspots": [],
    };

    if ($.isPlainObject(settings) === true) {
      _globals.settings = $.extend(true, {}, defaultSettings, settings);
    }
    else {
      _globals.settings = defaultSettings;
    }

    /* this object contains a number of functions to test for dependencies,
     * functies should return TRUE if the library/browser/etc is present
     */
    _globals.dependencies = {
      /* check for jQuery 1.6+ to be present */
      "jquery1.5+": function () {
        var jqv, jqv_main, jqv_sub;
        if (window.jQuery) {
          jqv = jQuery().jquery.split('.');
          jqv_main = parseInt(jqv[0], 10);
          jqv_sub = parseInt(jqv[1], 10);
          if (jqv_main > 1 || (jqv_main === 1 && jqv_sub >= 5)) {
            return true;
          }
          else {
            return false;
          }
        }
      },
      "jCanvas (http://calebevans.me/projects/jcanvas)": function () {
        return (typeof $.jCanvas !== "undefined");
      }
    };
    _helper.checkDependencies();

    //this object holds all plugins
    _globals.plugins = {};


    /* register globals
     * jQuerified elements start with $
     */

    /* the main element, originally the image, replaced by canvas during init */
    _globals.$element = $(element);
    _globals.$canvasElement = undefined;

    /* the canvas context */
    _globals.context = undefined;

    /* the image as a Javascript Image object */
    _globals.imageObj = undefined;

    /* the dimensions of the image object */
    _globals.imageWidth = undefined;
    _globals.imageHeight = undefined;

    /* the mouse start position */
    _globals.startPosition = undefined;

    /* inversion of the line */
    _globals.isCurveClockwise = 1;

    /* selected answer */
    _globals.selectedLine = undefined;

    /* answer index */
    _globals.answerIndex = 1;

    /* number of hotspots */
    _globals.hotspotsAsArray = [];

    /**
     * Init function
     **/
    publicObj.init = function () {
      var promise = new Promise(function (resolve) {
        var $canvas;
        /* wait until image loads */
        $(_globals.$element).one('load', function () {
          /* turn image into jCanvas
           */
          _globals.imageWidth = _globals.$element.width();
          _globals.imageHeight = _globals.$element.height();

          /* replace image with canvas html element */
          $canvas = $('<canvas class="cqCanvas" />');
          $canvas.attr('width', _globals.imageWidth);
          $canvas.attr('height', _globals.imageHeight);
          _globals.$element.after($canvas);

          //add clone of image to canvas
          $canvas.addLayer({
            "name": "image",
            "type": "image",
            "source": _globals.$element.attr('src'),
            "x": 0,
            "y": 0,
            "width": _globals.imageWidth,
            "height": _globals.imageHeight,
            "fromCenter": false
          }).drawLayers();

          //replace image
          _globals.$element.hide();
          _globals.$canvasElement = $canvas;

          /* attach event handlers to canvas */
          _globals.$canvasElement.on('mousedown', onCanvasMouseDown);
          _globals.$canvasElement.on('mouseup', onCanvasMouseUp);
          _globals.$canvasElement.on('mousemove', onCanvasMouseMove);

          /* draw hotspots */
          createHotspots();

          /* capture delete button */
          $('html').keyup(function (e) {
            if (e.keyCode === 46) {
              onDeleteKeyUp(e);
            }
          });

          _globals.$element.trigger('cqArrowQuestion.init', publicObj);
          _globals.$element.data('cqArrowQuestion', publicObj);
          resolve();
        }).each(function () {
          /* http://stackoverflow.com/questions/3877027/jquery-callback-on-image-load-even-when-the-image-is-cached */
          if (this.complete) {
            $(this).trigger('load');
          }
        });
      });

      return promise;
    };

    /**
     * Returns answer
     * @return string A comma separated list of hotspot ids, e.g. "ab,cd,ef"
     */
    publicObj.getAnswer = function () {
      var answer = [];
      var answerLayers = _globals.$canvasElement.getLayerGroup('answers');
      if (answerLayers !== undefined) {
        $.each(answerLayers, function (i, layer) {
          var isCurveClockwiseAppendix = '';
          switch (layer.type) {
            case 'quadratic':
            case 'line':
              isCurveClockwiseAppendix = layer.isCurveClockwise === 1 ? '*' : ''; //add character to remember isCurveClockwise
              answer[getLayerLineIndex(layer) - 1] = layer.hotspotPair + isCurveClockwiseAppendix;

              break;
          }
        });

        // If direction does not matter: Sort hotspot pairs alphabetically to ease the construct of feedback conditions.
        if ((_globals.settings.endArrow === false && _globals.settings.startArrow === false)
          || (_globals.settings.endArrow === true && _globals.settings.startArrow === true)) {
          $.each(answer, function (i, answerPair) {
            answer[i] = answerPair.split('').sort().join('');
          });
        }

        // If order of arrows does not matter: Sort them
        if (_globals.settings.lineNumbering === false) {
          answer.sort();
        }
      }

      return answer.join(',');
    };

    /**
     * Sets answer
     * @param {string} answerAsString A comma separated list of hotspot ids, e.g. "ab,cd,ef"
     */
    publicObj.setAnswer = function (answerAsString) {
      /* clear current answer */
      clearAnswer();

      if (answerAsString === '')
        return;

      /* add new answer */
      var answerAsArray = answerAsString.split(',');

      $.each(answerAsArray, function (answerIndex, hotspotPair) {
        var startPosition = getHotspotPosition(hotspotPair[0]);
        var endPosition = getHotspotPosition(hotspotPair[1]);
        var isCurveClockwise = typeof hotspotPair[2] === 'undefined' ? -1 : 1;
        drawAnswerLine(startPosition.x, startPosition.y, endPosition.x, endPosition.y, {
          "doRefreshCanvas": false,
          "isCurveClockwise": isCurveClockwise
        });
        addLineToAnswer(hotspotPair.substr(0, 2));
      });

      refreshCanvas();
    };

    /**
     * Registers a plugin
     * @param {string} name Name of plugin, must be unique
     * @param {object} object An object {("functions": {},) (, "hooks: {}) (, "targetcqArrows": [])}
     */
    publicObj.registerPlugin = function (name, object) {
      _helper.registerPlugin(name, object);
    };

    /**
     * Calls a plugin function, all additional arguments will be passed on
     * @param {string} cqArrowQuestion
     * @param {string} pluginFunctionName
     */
    publicObj.callPluginFunction = function (cqArrowQuestion, pluginFunctionName) {
      /* call function */
      _helper.callPluginFunction.apply(null, arguments);
    };

    /**
     * Called when use triggers mousemove event on canvas element
     * @param {object} e The event object
     **/
    function onCanvasMouseMove(e) {
      _helper.doHook('onBeforeCanvasMouseMove', e);
      var startPosition = getStartPosition();
      if (startPosition === undefined) {
        return;
      }

      var isCurveClockwise = e.ctrlKey === false ? -1 : 1;
      var currentPosition = getCurrentPosition(e);

      /* remove previous temp line */
      _globals.$canvasElement.removeLayerGroup('tempLine');

      /* draw temp line */
      drawAnswerLine(startPosition.x, startPosition.y, currentPosition.x, currentPosition.y, {
        "isCurveClockwise": isCurveClockwise
      });

      //call hook
      _helper.doHook('onCanvasMouseMove', e);
    }

    /**
     * Creates (and optionally, draws) hotspots
     */
    function createHotspots() {
      $.each(_globals.settings.hotspots, function (hotspotId, hotspotSettings) {
        var coords = hotspotSettings.coords.split(',');
        $.each(coords, function (i, coord) {
          coords[i] = parseInt(coord, 10);
        });
        var shape = hotspotSettings.shape;

        switch (shape) {
          case 'rect':
            /* remember hotspot specs so we can later find out if cursor is hovering */
            _globals.hotspotsAsArray.push([coords[0], coords[1], coords[2], coords[3], hotspotId]);

            /* draw hotspot */
            _globals.$canvasElement.addLayer({
              "type": "rectangle",
              "name": "hotspot_" + hotspotId,
              "groups": ["hotspots"],
              "x": parseInt(coords[0], 10),
              "y": parseInt(coords[1], 10),
              "fromCenter": false,
              "width": coords[2] - coords[0],
              "height": coords[3] - coords[1],
              "strokeStyle": _globals.settings.hotspotColor,
              "visible": _globals.settings.showHotspots
            });

            /* optionally, draw hotspot id */
            if (_globals.settings.showHotspotLabels === true) {
              _globals.$canvasElement.addLayer({
                "type": "text",
                "groups": ["hotspots"],
                "strokeStyle": _globals.settings.hotspotColor,
                "strokeWidth": 1,
                "x": coords[0],
                "y": coords[1],
                "fontSize": 14,
                "fontFamily": 'Verdana, sans-serif',
                "text": hotspotId
              });
            }

            break;

          default:
            console.warning('The arrow question currently only supports rectangle hotspots');
            break;

        }
      });
    }

    /**
     * Calculates the postion of the line
     * @param {type} x1
     * @param {type} y1
     * @param {type} x2
     * @param {type} y2
     * @param {type} options
     * @returns {object}
     */
    function getAnswerLineConfig(x1, y1, x2, y2, options) {
      var answerLineConfig = {
        "x1": x1,
        "y1": y1,
        "x2": x2,
        "y2": y2,
        "name": options.name === undefined ? "line_" + _globals.answerIndex : options.name,
        "groups": options.groups === undefined ? ["tempLine", "lines"] : options.groups,
        "endArrow": _globals.settings.endArrow,
        "startArrow": _globals.settings.startArrow,
        "arrowRadius": 8,
        "arrowAngle": 90,
        "strokeWidth": 3,
        "rounded": true,
        "strokeStyle": options.lineColor === undefined ? _globals.settings.lineColor : options.lineColor,
        "lineStyle": 1
      };

      var tempDx, tempDy, tempLength, tempHeight, tempAlpha, tempX1, tempY1;

      switch (_globals.settings.lineStyle) {
        case 'curved':
          /* set options */
          answerLineConfig.type = "quadratic";
          answerLineConfig.isCurveClockwise = options.isCurveClockwise === undefined ? -1 : options.isCurveClockwise;

          /* do calculations */
          tempDx = x2 - x1;
          tempDy = y1 - y2;
          tempLength = Math.sqrt(tempDx * tempDx + tempDy * tempDy);
          tempHeight = answerLineConfig.isCurveClockwise * tempLength / 2;
          tempAlpha = Math.atan(tempDy / tempDx);


          tempX1 = x1 + (x2 < x1 ? -1 : 1) * Math.cos(tempAlpha) * tempLength / 2;
          tempY1 = y1 - (x2 < x1 ? -1 : 1) * Math.sin(tempAlpha) * tempLength / 2;

          answerLineConfig.cx1 = tempX1 + Math.sin(tempAlpha) * tempHeight;
          answerLineConfig.cy1 = tempY1 + Math.cos(tempAlpha) * tempHeight;

          answerLineConfig.xText = tempX1 + Math.sin(tempAlpha) * tempHeight / 2;
          answerLineConfig.yText = tempY1 + Math.cos(tempAlpha) * tempHeight / 2;

          answerLineConfig.xDelete = answerLineConfig.xText;
          answerLineConfig.yDelete = answerLineConfig.yText;
          break;

        case 'straight':
          /* set options */
          answerLineConfig.type = "line";

          /* do calculations */
          answerLineConfig.xText = (x2 + x1) / 2;
          answerLineConfig.yText = (y1 + y2) / 2;

          answerLineConfig.xDelete = answerLineConfig.xText;
          answerLineConfig.yDelete = answerLineConfig.yText;
          break;
      }

      return answerLineConfig;
    }



    /**
     * Draws an answer on the canvas
     * @param {integer} x1
     * @param {integer} y1
     * @param {integer} x2
     * @param {integer} y2
     * @param {object} options Additional options {
     *                                              "isCurveClockwise": 1 or -1 (default: -1),
     *                                              "doRefreshCanvas": boolean (default: true),
     *                                              "lineColor": string (default _globals.settings.lineColor)
     *                                            }
     */
    function drawAnswerLine(x1, y1, x2, y2, options) {
      var answerLineConfig = getAnswerLineConfig(x1, y1, x2, y2, options);

      /* draw line */
      _globals.$canvasElement.addLayer(answerLineConfig);

      /* draw text */
      _globals.$canvasElement.addLayer({
        "type": "ellipse",
        "name": "circle_" + _globals.answerIndex,
        "groups": ["tempLine", "texts"],
        "fillStyle": answerLineConfig.strokeStyle,
        "strokeStyle": answerLineConfig.strokeStyle,
        "x": answerLineConfig.xText,
        "y": answerLineConfig.yText,
        "width": (_globals.settings.lineNumbering === true ? 14 : 10),
        "height": (_globals.settings.lineNumbering === true ? 14 : 10),
        "visible": true
      });

      _globals.$canvasElement.addLayer({
        "type": "text",
        "name": "text_" + _globals.answerIndex,
        "groups": ["tempLine", "texts"],
        "fillStyle": "#fff",
        "strokeStyle": "#fff",
        "strokeWidth": 1,
        "x": answerLineConfig.xText,
        "y": answerLineConfig.yText,
        "fontSize": 10,
        "fontFamily": 'Verdana, sans-serif',
        "text": _globals.answerIndex.toString(),
        "visible": _globals.settings.lineNumbering
      });

      /* draw delete button */
      _globals.$canvasElement.addLayer({
        "type": "ellipse",
        "action": "delete", // Not in jcanvas lib, used for deleting.
        "name": "deleteCircle_" + _globals.answerIndex,
        "groups": ["tempLine", "ui"],
        "fillStyle": '#f00',
        "strokeStyle": '#f00',
        "strokeWidth": 2,
        "x": answerLineConfig.xDelete,
        "y": answerLineConfig.yDelete,
        "width": 14, "height": 14,
        "fontSize": 10,
        "fontFamily": 'Verdana, sans-serif',
        "text": 'x',
        "visible": false
      });

      //x
      _globals.$canvasElement.addLayer({
        "type": "text",
        "action": "delete", // Not in jcanvas lib, used for deleting.
        "name": "deleteText_" + _globals.answerIndex,
        "groups": ["tempLine", "ui"],
        "fillStyle": '#fff',
        "strokeStyle": '#fff',
        "strokeWidth": 1,
        "x": answerLineConfig.xDelete,
        "y": answerLineConfig.yDelete,
        "fontSize": 10,
        "fontFamily": 'Verdana, sans-serif',
        "text": 'x',
        "visible": false
      });

      if (options.refreshCanvas !== false) {
        refreshCanvas();
      }
    }

    /**
     * Adds answer unit to answer
     * @param {string} hotspotPair The ids of the hotspots, e.g. 'ab'
     */
    function addLineToAnswer(hotspotPair) {
      /* add unit to specific groups */
      _globals.$canvasElement.setLayer("line_" + _globals.answerIndex, {
        "groups": ["answers", "lines", _globals.answerIndex],
        "hotspotPair": hotspotPair
      });

      _globals.$canvasElement.setLayer("text_" + _globals.answerIndex, {
        "groups": ["answers", "texts", _globals.answerIndex]
      });

      _globals.$canvasElement.setLayer("circle_" + _globals.answerIndex, {
        "groups": ["answers", "texts", _globals.answerIndex]
      });

      _globals.$canvasElement.setLayer("deleteText_" + _globals.answerIndex, {
        "groups": ["answers", "ui", _globals.answerIndex]
      });

      _globals.$canvasElement.setLayer("deleteCircle_" + _globals.answerIndex, {
        "groups": ["answers", "ui", _globals.answerIndex]
      });

      /* add event handlers */
      _globals.$canvasElement.setLayerGroup(_globals.answerIndex, {
        "mouseover": onLineMouseOver,
        "mouseout": onLineMouseOut,
        "click": onLineMouseClick
      });

      /* set and update selected answer index  */
      setSelectedLine(_globals.answerIndex);
      _globals.answerIndex++;

      /* call hook */
      _helper.doHook('onAddLineToAnswer', _globals.answerIndex, hotspotPair);
    }

    /**
     * Deletes an answer unit
     * @param {index} answerUnitIndex
     */
    function removeLineFromAnswer(answerUnitIndex) {
      if (answerUnitIndex < 1) {
        return;
      }

      var index, layerNames, answerUnitLayers;

      /* remove visual representation */
      _globals.$canvasElement.removeLayerGroup(answerUnitIndex);

      /* renumber layers with higher index */
      for (index = answerUnitIndex + 1; index < _globals.answerIndex; index++) {
        answerUnitLayers = _globals.$canvasElement.getLayerGroup(index);
        layerNames = [];

        /* we want to iterate of names (and not on answerUnitLayers, which will
         * shrink as we remove layers */
        $.each(answerUnitLayers, function (i, layer) {
          layerNames.push(layer.name);
        });

        $.each(layerNames, function (i, layerName) {
          /* remove layer from group with index and add it to group with index-1 */
          _globals.$canvasElement.removeLayerFromGroup(layerName, index);
          _globals.$canvasElement.addLayerToGroup(layerName, index - 1);

          /* change text layer's caption */
          if (layerName.indexOf('text_') !== -1) {
            //change number
            _globals.$canvasElement.setLayer("text_" + index, {
              "text": index - 1
            });
          }

          /* change layer name */
          _globals.$canvasElement.setLayer(layerName, {
            "name": layerName.substring(0, layerName.indexOf('_')) + '_' + (index - 1)
          });
        });
      }

      /* lower layer counter */
      _globals.answerIndex--;
      setSelectedLine(answerUnitIndex - 1);

      /* refresh canvas and trigger event */
      refreshCanvas();
      _helper.doHook('onRemoveLineFromAnswer', _globals.answerIndex, answerUnitIndex);
    }

    /**
     * Called when use triggers mousedown event on canvas element
     * @param {object} e The event object
     **/
    function onCanvasMouseDown(e) {
      var currentPosition = getCurrentPosition(e);

      if (currentPosition.inHotspotKey !== undefined) {
        setStartPosition(currentPosition);
      }
    }

    /**
     * Called when use triggers mouseup event on canvas element
     * @param {object} e The event object
     **/
    function onCanvasMouseUp(e) {
      var currentPosition = getCurrentPosition(e);

      /* decide what to do: turn temp answer into answer or not */
      if (currentPosition.inHotspotKey !== undefined) {
        /* turn temp answer into answer */
        if (getStartPosition().inHotspotKey !== currentPosition.inHotspotKey) {
          addLineToAnswer(getStartPosition().inHotspotKey + currentPosition.inHotspotKey);
        }
      }
      else {
        /* clear temp answer */
        _globals.$canvasElement.removeLayerGroup('tempLine');
      }

      /* reset line start position */
      setStartPosition(undefined);
    }

    /**
     * Called when user hovers line
     * @param {object} layer The line jDraw layer object
     */
    function onLineMouseOver(layer) {
      var answerUnitIndex = getLayerLineIndex(layer);

      _globals.$canvasElement.setLayer('line_' + answerUnitIndex, {
        "strokeStyle": _globals.settings.lineSelectedColor
      });

      _globals.$canvasElement.setLayer('deleteText_' + answerUnitIndex, {
        "visible": true
      });

      _globals.$canvasElement.setLayer('deleteCircle_' + answerUnitIndex, {
        "visible": true
      });
    }

    /**
     * Called when user unhovers line
     * @param {object} layer The line jDraw layer object
     */
    function onLineMouseOut(layer) {
      var answerUnitIndex = getLayerLineIndex(layer);

      _globals.$canvasElement.setLayer('line_' + answerUnitIndex, {
        "strokeStyle": _globals.settings.lineColor
      });

      _globals.$canvasElement.setLayer('deleteText_' + answerUnitIndex, {
        "visible": false
      });

      _globals.$canvasElement.setLayer('deleteCircle_' + answerUnitIndex, {
        "visible": false
      });
    }

    /**
     * Called when user clicks answer unit
     * @param {object} layer The line jDraw layer object
     */
    function onLineMouseClick(layer) {
      /* select answer */
      setSelectedLine(getLayerLineIndex(layer));

      /* remove? */
      if (layer.action === "delete") {
        onLineUIMouseClick(layer);
      }

      return;
    }

    /**
     * Called when user clicks on UI element of answer unit
     * @param {object} layer The line jDraw layer object
     */
    function onLineUIMouseClick(layer) {
      var answerIndex = getLayerLineIndex(layer);

      /* currently only delete button in UI */
      removeLineFromAnswer(answerIndex);
    }

    /**
     * Called when user pressed delete button
     * @param {event} e
     * @todo Implement
     */
    function onDeleteKeyUp(e) {
      /* currently only delete button in UI */
      removeLineFromAnswer(getSelectedLineIndex());
    }

    /**
     * Sets the selected answer
     * @param {integer} index
     */
    function setSelectedLine(index) {
      _globals.selectedLineIndex = index;

      /* change appearance */

      //reset all elements
      _globals.$canvasElement.setLayerGroup('lines', {
        "strokeStyle": _globals.settings.lineColor
      });

//      _globals.$canvasElement.setLayerGroup('texts', {
//        "strokeStyle": _globals.settings.lineColor,
//        "fillStyle": _globals.settings.lineColor
//      });

      _globals.$canvasElement.setLayerGroup('ui', {
        "visible": false
      });

      _globals.$canvasElement.setLayerGroup('ui', {
        "visible": false
      });

      //set selected layer
      _globals.$canvasElement.setLayer('line_' + index, {
        "strokeStyle": _globals.settings.lineSelectedColor
      });

      _globals.$canvasElement.setLayer('text_' + index, {
        "fillStyle": "#fff",
        "strokeStyle": "#fff"
      });
//
//      _globals.$canvasElement.setLayer('text_' + index, {
//        "strokeStyle": _globals.settings.lineSelectedColor,
//        "fillStyle": _globals.settings.lineSelectedColor
//      });



      refreshCanvas();
    }

    /**
     * Returns the selected answer
     * @return integer
     */
    function getSelectedLineIndex() {
      return _globals.selectedLineIndex;
    }

    /**
     * Returns a layer's answer index
     * @param {object} layer
     * @returns {integer}
     */
    function getLayerLineIndex(layer) {
      return parseInt(layer.name.split('_')[1]);
    }

    /**
     * Returns the center coordinates of a hotspot
     * @param {string} hotspotId
     * @return object {"x": int, "y": int}
     */
    function getHotspotPosition(hotspotId) {
      var hotspot = _globals.$canvasElement.getLayer("hotspot_" + hotspotId);

      if (hotspot) {
        return {"x": hotspot.x + hotspot.width / 2, "y": hotspot.y + hotspot.height / 2}
      }
      return null;
    }

    /**
     * Returns end position of line. This can be the mouse position, but also
     *  a 'snapped' position.
     * @param {object} e Mouse event object
     * @return {object} {"x": int, "y": int, "inHotspot": boolean}
     */
    function getCurrentPosition(e) {
      var position = {"x": undefined, "y": undefined, "inHotspotKey": undefined};
      var mouseX = e.offsetX;
      var mouseY = e.offsetY;
      var i, hotspotCoords;

      /* check if mouse inside hotspot
       * @todo use hotspot mouseover/out for this, currently event not falling
       * through because of line being drawn
       */
      for (i = _globals.hotspotsAsArray.length - 1; i >= 0; i--) {
        hotspotCoords = _globals.hotspotsAsArray[i];

        if (mouseX >= hotspotCoords[0] && mouseX <= hotspotCoords[2]) {
          if (mouseY >= hotspotCoords[1] && mouseY <= hotspotCoords[3]) {
            position.x = hotspotCoords[0] + (hotspotCoords[2] - hotspotCoords[0]) / 2;
            position.y = hotspotCoords[1] + (hotspotCoords[3] - hotspotCoords[1]) / 2;
            position.inHotspotKey = hotspotCoords[4];
            break;
          }
        }
      }

      if (position.inHotspotKey === undefined) {
        position.x = mouseX;
        position.y = mouseY;
      }
      return position;
    }

    /**
     * Saves line start position
     * @param {object} position {"x": int, "y": int, "inHotspotKey": string}
     */
    function setStartPosition(position) {
      _globals.startPosition = position;
    }

    /**
     * Returns line start position
     * @returns {object} See setStartPosition
     */
    function getStartPosition() {
      return _globals.startPosition;
    }

    /**
     * Redraws the canvas
     */
    function refreshCanvas() {
      _globals.$canvasElement.drawLayers();
    }

    /**
     * Clears the canvas
     */
    function clearAnswer() {
      _globals.$canvasElement.removeLayerGroup("answers");
      _globals.answerIndex = 1;
      refreshCanvas();
    }

    /* initialize cqArrowQuestion
     */
    $(element).trigger('cqArrowQuestion.beforeInit', publicObj, element, settings); //trigger event on document
    return publicObj.init();
  };
})(jQuery);

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc