<commit_before>const utils = require('../utils');

function init (scope) {
  const {
    actions,
    /** @lends module:interact */
    interact,
    /** @lends Interactable */
    Interactable,
    Interaction,
    defaults,
  } = scope;

  let dynamicDrop = false;

  Interaction.signals.on('after-action-start', function ({ interaction, event }) {
    if (interaction.prepared.name !== 'drag') { return; }

    // reset active dropzones
    interaction.activeDrops = null;
    interaction.dropEvents = null;

    if (!interaction.dynamicDrop) {
      interaction.activeDrops = getActiveDrops(scope, interaction.element);
    }

    const dragEvent = interaction.prevEvent;
    const dropEvents = getDropEvents(interaction, event, dragEvent);

    if (dropEvents.activate) {
      fireActivationEvents(interaction.activeDrops, dropEvents.activate);
    }
  });

  Interaction.signals.on('action-move', arg => onEventCreated(arg, scope, dynamicDrop));
  Interaction.signals.on('action-end' , arg => onEventCreated(arg, scope, dynamicDrop));

  Interaction.signals.on('after-action-move', function ({ interaction }) {
    if (interaction.prepared.name !== 'drag') { return; }

    fireDropEvents(interaction, interaction.dropEvents);
  });

  Interaction.signals.on('after-action-end', function ({ interaction }) {
    if (interaction.prepared.name === 'drag') {
      fireDropEvents(interaction, interaction.dropEvents);
    }
  });

  Interaction.signals.on('stop-drag', function ({ interaction }) {
    interaction.activeDrops = null;
    interaction.dropEvents = null;
  });

  /**
   * ```js
   * interact(target)
   * .dropChecker(function(dragEvent,         // related dragmove or dragend event
   *                       event,             // TouchEvent/PointerEvent/MouseEvent
   *                       dropped,           // bool result of the default checker
   *                       dropzone,          // dropzone Interactable
   *                       dropElement,       // dropzone elemnt
   *                       draggable,         // draggable Interactable
   *                       draggableElement) {// draggable element
   *
   *   return dropped && event.target.hasAttribute('allow-drop');
   * }
   * ```
   *
   * ```js
   * interact('.drop').dropzone({
   *   accept: '.can-drop' || document.getElementById('single-drop'),
   *   overlap: 'pointer' || 'center' || zeroToOne
   * }
   * ```
   *
   * Returns or sets whether draggables can be dropped onto this target to
   * trigger drop events
   *
   * Dropzones can receive the following events:
   *  - `dropactivate` and `dropdeactivate` when an acceptable drag starts and ends
   *  - `dragenter` and `dragleave` when a draggable enters and leaves the dropzone
   *  - `dragmove` when a draggable that has entered the dropzone is moved
   *  - `drop` when a draggable is dropped into this dropzone
   *
   * Use the `accept` option to allow only elements that match the given CSS
   * selector or element. The value can be:
   *
   *  - **an Element** - only that element can be dropped into this dropzone.
   *  - **a string**, - the element being dragged must match it as a CSS selector.
   *  - **`null`** - accept options is cleared - it accepts any element.
   *
   * Use the `overlap` option to set how drops are checked for. The allowed
   * values are:
   *
   *   - `'pointer'`, the pointer must be over the dropzone (default)
   *   - `'center'`, the draggable element's center must be over the dropzone
   *   - a number from 0-1 which is the `(intersection area) / (draggable area)`.
   *   e.g. `0.5` for drop to happen when half of the area of the draggable is
   *   over the dropzone
   *
   * Use the `checker` option to specify a function to check if a dragged element
   * is over this Interactable.
   *
   * @param {boolean | object | null} [options] The new options to be set.
   * @return {boolean | Interactable} The current setting or this Interactable
   */
  Interactable.prototype.dropzone = function (options) {
    if (utils.is.object(options)) {
      this.options.drop.enabled = options.enabled === false? false: true;

      if (utils.is.function(options.ondrop)          ) { this.events.ondrop           = options.ondrop          ; }
      if (utils.is.function(options.ondropactivate)  ) { this.events.ondropactivate   = options.ondropactivate  ; }
      if (utils.is.function(options.ondropdeactivate)) { this.events.ondropdeactivate = options.ondropdeactivate; }
      if (utils.is.function(options.ondragenter)     ) { this.events.ondragenter      = options.ondragenter     ; }
      if (utils.is.function(options.ondragleave)     ) { this.events.ondragleave      = options.ondragleave     ; }
      if (utils.is.function(options.ondropmove)      ) { this.events.ondropmove       = options.ondropmove      ; }

      if (/^(pointer|center)$/.test(options.overlap)) {
        this.options.drop.overlap = options.overlap;
      }
      else if (utils.is.number(options.overlap)) {
        this.options.drop.overlap = Math.max(Math.min(1, options.overlap), 0);
      }
      if ('accept' in options) {
        this.options.drop.accept = options.accept;
      }
      if ('checker' in options) {
        this.options.drop.checker = options.checker;
      }

      return this;
    }

    if (utils.is.bool(options)) {
      this.options.drop.enabled = options;

      if (!options) {
        this.ondragenter = this.ondragleave = this.ondrop
          = this.ondropactivate = this.ondropdeactivate = null;
      }

      return this;
    }

    return this.options.drop;
  };

  Interactable.prototype.dropCheck = function (dragEvent, event, draggable, draggableElement, dropElement, rect) {
    let dropped = false;

    // if the dropzone has no rect (eg. display: none)
    // call the custom dropChecker or just return false
    if (!(rect = rect || this.getRect(dropElement))) {
      return (this.options.drop.checker
        ? this.options.drop.checker(dragEvent, event, dropped, this, dropElement, draggable, draggableElement)
        : false);
    }

    const dropOverlap = this.options.drop.overlap;

    if (dropOverlap === 'pointer') {
      const origin = utils.getOriginXY(draggable, draggableElement, 'drag');
      const page = utils.pointer.getPageXY(dragEvent);

      page.x += origin.x;
      page.y += origin.y;

      const horizontal = (page.x > rect.left) && (page.x < rect.right);
      const vertical   = (page.y > rect.top ) && (page.y < rect.bottom);

      dropped = horizontal && vertical;
    }

    const dragRect = draggable.getRect(draggableElement);

    if (dragRect && dropOverlap === 'center') {
      const cx = dragRect.left + dragRect.width  / 2;
      const cy = dragRect.top  + dragRect.height / 2;

      dropped = cx >= rect.left && cx <= rect.right && cy >= rect.top && cy <= rect.bottom;
    }

    if (dragRect && utils.is.number(dropOverlap)) {
      const overlapArea  = (Math.max(0, Math.min(rect.right , dragRect.right ) - Math.max(rect.left, dragRect.left))
                            * Math.max(0, Math.min(rect.bottom, dragRect.bottom) - Math.max(rect.top , dragRect.top )));

      const overlapRatio = overlapArea / (dragRect.width * dragRect.height);

      dropped = overlapRatio >= dropOverlap;
    }

    if (this.options.drop.checker) {
      dropped = this.options.drop.checker(dragEvent, event, dropped, this, dropElement, draggable, draggableElement);
    }

    return dropped;
  };

  Interactable.signals.on('unset', function ({ interactable }) {
    interactable.dropzone(false);
  });

  Interactable.settingsMethods.push('dropChecker');

  Interaction.signals.on('new', function (interaction) {
    interaction.dropTarget      = null; // the dropzone a drag target might be dropped into
    interaction.dropElement     = null; // the element at the time of checking
    interaction.prevDropTarget  = null; // the dropzone that was recently dragged away from
    interaction.prevDropElement = null; // the element at the time of checking
    interaction.dropEvents      = null; // the dropEvents related to the current drag event
    interaction.activeDrops     = null; // an array of { dropzone, element, rect }
  });

  Interaction.signals.on('stop', function ({ interaction }) {
    interaction.dropTarget = interaction.dropElement =
      interaction.prevDropTarget = interaction.prevDropElement = null;
  });

  /**
   * Returns or sets whether the dimensions of dropzone elements are calculated
   * on every dragmove or only on dragstart for the default dropChecker
   *
   * @param {boolean} [newValue] True to check on each move. False to check only
   * before start
   * @return {boolean | interact} The current setting or interact
   */
  interact.dynamicDrop = function (newValue) {
    if (utils.is.bool(newValue)) {
      //if (dragging && dynamicDrop !== newValue && !newValue) {
        //calcRects(dropzones);
      //}

      dynamicDrop = newValue;

      return interact;
    }
    return dynamicDrop;
  };

  utils.arr.merge(Interactable.eventTypes, [
    'dragenter',
    'dragleave',
    'dropactivate',
    'dropdeactivate',
    'dropmove',
    'drop',
  ]);
  actions.methodDict.drop = 'dropzone';

  defaults.drop = module.exports.defaults;
}

function collectDrops ({ interactables }, draggableElement) {
  const drops = [];

  // collect all dropzones and their elements which qualify for a drop
  for (const dropzone of interactables) {
    if (!dropzone.options.drop.enabled) { continue; }

    const accept = dropzone.options.drop.accept;

    // test the draggable draggableElement against the dropzone's accept setting
    if ((utils.is.element(accept) && accept !== draggableElement)
        || (utils.is.string(accept)
        && !utils.dom.matchesSelector(draggableElement, accept))) {

      continue;
    }

    // query for new elements if necessary
    const dropElements = utils.is.string(dropzone.target)
      ? dropzone._context.querySelectorAll(dropzone.target)
      : [dropzone.target];

    for (const dropzoneElement of dropElements) {
      if (dropzoneElement !== draggableElement) {
        drops.push({
          dropzone,
          element: dropzoneElement,
        });
      }
    }
  }

  return drops;
}

function fireActivationEvents (activeDrops, event) {
  let prevElement;

  // loop through all active dropzones and trigger event
  for (const { dropzone, element } of activeDrops) {

    // prevent trigger of duplicate events on same element
    if (element !== prevElement) {
      // set current element as event target
      event.target = element;
      dropzone.fire(event);
    }
    prevElement = element;
  }
}

// return a new array of possible drops. getActiveDrops should always be
// called when a drag has just started or a drag event happens while
// dynamicDrop is true
function getActiveDrops (scope, dragElement) {
  // get dropzones and their elements that could receive the draggable
  const activeDrops = collectDrops(scope, dragElement);

  for (const activeDrop of activeDrops) {
    activeDrop.rect = activeDrop.dropzone.getRect(activeDrop.element);
  }

  return activeDrops;
}

function getDrop ({ activeDrops, target: draggable, element: dragElement }, dragEvent, pointerEvent) {
  const { interaction } = dragEvent;

  const validDrops = [];

  // collect all dropzones and their elements which qualify for a drop
  for (const { dropzone, element: dropzoneElement, rect } of activeDrops) {
    validDrops.push(dropzone.dropCheck(dragEvent, pointerEvent, draggable, dragElement, dropzoneElement, rect)
      ? dropzoneElement
      : null);
  }

  // get the most appropriate dropzone based on DOM depth and order
  const dropIndex = utils.dom.indexOfDeepestElement(validDrops);

  return interaction.activeDrops[dropIndex] || null;
}

function getDropEvents (interaction, pointerEvent, dragEvent) {
  const dropEvents = {
    enter     : null,
    leave     : null,
    activate  : null,
    deactivate: null,
    move      : null,
    drop      : null,
  };

  const tmpl = {
    dragEvent,
    interaction,
    target       : interaction.dropElement,
    dropzone     : interaction.dropTarget,
    relatedTarget: dragEvent.target,
    draggable    : dragEvent.interactable,
    timeStamp    : dragEvent.timeStamp,
  };

  if (interaction.dropElement !== interaction.prevDropElement) {
    // if there was a prevDropTarget, create a dragleave event
    if (interaction.prevDropTarget) {
      dropEvents.leave = utils.extend({ type: 'dragleave' }, tmpl);

      dragEvent.dragLeave    = dropEvents.leave.target   = interaction.prevDropElement;
      dragEvent.prevDropzone = dropEvents.leave.dropzone = interaction.prevDropTarget;
    }
    // if the dropTarget is not null, create a dragenter event
    if (interaction.dropTarget) {
      dropEvents.enter = {
        dragEvent,
        interaction,
        target       : interaction.dropElement,
        dropzone     : interaction.dropTarget,
        relatedTarget: dragEvent.target,
        draggable    : dragEvent.interactable,
        timeStamp    : dragEvent.timeStamp,
        type         : 'dragenter',
      };

      dragEvent.dragEnter = interaction.dropElement;
      dragEvent.dropzone = interaction.dropTarget;
    }
  }

  if (dragEvent.type === 'dragend' && interaction.dropTarget) {
    dropEvents.drop = utils.extend({ type: 'drop' }, tmpl);

    dragEvent.dropzone = interaction.dropTarget;
    dragEvent.relatedTarget = interaction.dropElement;
  }
  if (dragEvent.type === 'dragstart') {
    dropEvents.activate = utils.extend({ type: 'dropactivate' }, tmpl);

    dropEvents.activate.target   = null;
    dropEvents.activate.dropzone = null;
  }
  if (dragEvent.type === 'dragend') {
    dropEvents.deactivate = utils.extend({ type: 'dropdeactivate' }, tmpl);

    dropEvents.deactivate.target   = null;
    dropEvents.deactivate.dropzone = null;
  }
  if (dragEvent.type === 'dragmove' && interaction.dropTarget) {
    dropEvents.move = utils.extend({
      dragmove     : dragEvent,
      type         : 'dropmove',
    }, tmpl);

    dragEvent.dropzone = interaction.dropTarget;
  }

  return dropEvents;
}

function fireDropEvents (interaction, dropEvents) {
  const {
    activeDrops,
    prevDropTarget,
    dropTarget,
    dropElement,
  } = interaction;

  if (dropEvents.leave) { prevDropTarget.fire(dropEvents.leave); }
  if (dropEvents.move ) {     dropTarget.fire(dropEvents.move ); }
  if (dropEvents.enter) {     dropTarget.fire(dropEvents.enter); }
  if (dropEvents.drop ) {     dropTarget.fire(dropEvents.drop ); }

  if (dropEvents.deactivate) {
    fireActivationEvents(activeDrops, dropEvents.deactivate);
  }

  interaction.prevDropTarget  = dropTarget;
  interaction.prevDropElement = dropElement;
}

function onEventCreated ({ interaction, iEvent, event }, scope, dynamicDrop) {
  if (iEvent.type !== 'dragmove' && iEvent.type !== 'dragend') { return; }

  if (dynamicDrop) {
    interaction.activeDrops = getActiveDrops(scope, interaction.element);
  }

  const dragEvent = iEvent;
  const dropResult = getDrop(interaction, dragEvent, event);

  interaction.dropTarget  = dropResult && dropResult.dropzone;
  interaction.dropElement = dropResult && dropResult.element;

  interaction.dropEvents = getDropEvents(interaction, event, dragEvent);
}

module.exports = {
  init,
  getActiveDrops,
  getDrop,
  getDropEvents,
  fireDropEvents,
  defaults: {
    enabled: false,
    accept : null,
    overlap: 'pointer',
  },
};
<commit_msg>drop: do nothing on Interactable unset<commit_after>const utils = require('../utils');

function init (scope) {
  const {
    actions,
    /** @lends module:interact */
    interact,
    /** @lends Interactable */
    Interactable,
    Interaction,
    defaults,
  } = scope;

  let dynamicDrop = false;

  Interaction.signals.on('after-action-start', function ({ interaction, event }) {
    if (interaction.prepared.name !== 'drag') { return; }

    // reset active dropzones
    interaction.activeDrops = null;
    interaction.dropEvents = null;

    if (!interaction.dynamicDrop) {
      interaction.activeDrops = getActiveDrops(scope, interaction.element);
    }

    const dragEvent = interaction.prevEvent;
    const dropEvents = getDropEvents(interaction, event, dragEvent);

    if (dropEvents.activate) {
      fireActivationEvents(interaction.activeDrops, dropEvents.activate);
    }
  });

  Interaction.signals.on('action-move', arg => onEventCreated(arg, scope, dynamicDrop));
  Interaction.signals.on('action-end' , arg => onEventCreated(arg, scope, dynamicDrop));

  Interaction.signals.on('after-action-move', function ({ interaction }) {
    if (interaction.prepared.name !== 'drag') { return; }

    fireDropEvents(interaction, interaction.dropEvents);
  });

  Interaction.signals.on('after-action-end', function ({ interaction }) {
    if (interaction.prepared.name === 'drag') {
      fireDropEvents(interaction, interaction.dropEvents);
    }
  });

  Interaction.signals.on('stop-drag', function ({ interaction }) {
    interaction.activeDrops = null;
    interaction.dropEvents = null;
  });

  /**
   * ```js
   * interact(target)
   * .dropChecker(function(dragEvent,         // related dragmove or dragend event
   *                       event,             // TouchEvent/PointerEvent/MouseEvent
   *                       dropped,           // bool result of the default checker
   *                       dropzone,          // dropzone Interactable
   *                       dropElement,       // dropzone elemnt
   *                       draggable,         // draggable Interactable
   *                       draggableElement) {// draggable element
   *
   *   return dropped && event.target.hasAttribute('allow-drop');
   * }
   * ```
   *
   * ```js
   * interact('.drop').dropzone({
   *   accept: '.can-drop' || document.getElementById('single-drop'),
   *   overlap: 'pointer' || 'center' || zeroToOne
   * }
   * ```
   *
   * Returns or sets whether draggables can be dropped onto this target to
   * trigger drop events
   *
   * Dropzones can receive the following events:
   *  - `dropactivate` and `dropdeactivate` when an acceptable drag starts and ends
   *  - `dragenter` and `dragleave` when a draggable enters and leaves the dropzone
   *  - `dragmove` when a draggable that has entered the dropzone is moved
   *  - `drop` when a draggable is dropped into this dropzone
   *
   * Use the `accept` option to allow only elements that match the given CSS
   * selector or element. The value can be:
   *
   *  - **an Element** - only that element can be dropped into this dropzone.
   *  - **a string**, - the element being dragged must match it as a CSS selector.
   *  - **`null`** - accept options is cleared - it accepts any element.
   *
   * Use the `overlap` option to set how drops are checked for. The allowed
   * values are:
   *
   *   - `'pointer'`, the pointer must be over the dropzone (default)
   *   - `'center'`, the draggable element's center must be over the dropzone
   *   - a number from 0-1 which is the `(intersection area) / (draggable area)`.
   *   e.g. `0.5` for drop to happen when half of the area of the draggable is
   *   over the dropzone
   *
   * Use the `checker` option to specify a function to check if a dragged element
   * is over this Interactable.
   *
   * @param {boolean | object | null} [options] The new options to be set.
   * @return {boolean | Interactable} The current setting or this Interactable
   */
  Interactable.prototype.dropzone = function (options) {
    if (utils.is.object(options)) {
      this.options.drop.enabled = options.enabled === false? false: true;

      if (utils.is.function(options.ondrop)          ) { this.events.ondrop           = options.ondrop          ; }
      if (utils.is.function(options.ondropactivate)  ) { this.events.ondropactivate   = options.ondropactivate  ; }
      if (utils.is.function(options.ondropdeactivate)) { this.events.ondropdeactivate = options.ondropdeactivate; }
      if (utils.is.function(options.ondragenter)     ) { this.events.ondragenter      = options.ondragenter     ; }
      if (utils.is.function(options.ondragleave)     ) { this.events.ondragleave      = options.ondragleave     ; }
      if (utils.is.function(options.ondropmove)      ) { this.events.ondropmove       = options.ondropmove      ; }

      if (/^(pointer|center)$/.test(options.overlap)) {
        this.options.drop.overlap = options.overlap;
      }
      else if (utils.is.number(options.overlap)) {
        this.options.drop.overlap = Math.max(Math.min(1, options.overlap), 0);
      }
      if ('accept' in options) {
        this.options.drop.accept = options.accept;
      }
      if ('checker' in options) {
        this.options.drop.checker = options.checker;
      }

      return this;
    }

    if (utils.is.bool(options)) {
      this.options.drop.enabled = options;

      if (!options) {
        this.ondragenter = this.ondragleave = this.ondrop
          = this.ondropactivate = this.ondropdeactivate = null;
      }

      return this;
    }

    return this.options.drop;
  };

  Interactable.prototype.dropCheck = function (dragEvent, event, draggable, draggableElement, dropElement, rect) {
    let dropped = false;

    // if the dropzone has no rect (eg. display: none)
    // call the custom dropChecker or just return false
    if (!(rect = rect || this.getRect(dropElement))) {
      return (this.options.drop.checker
        ? this.options.drop.checker(dragEvent, event, dropped, this, dropElement, draggable, draggableElement)
        : false);
    }

    const dropOverlap = this.options.drop.overlap;

    if (dropOverlap === 'pointer') {
      const origin = utils.getOriginXY(draggable, draggableElement, 'drag');
      const page = utils.pointer.getPageXY(dragEvent);

      page.x += origin.x;
      page.y += origin.y;

      const horizontal = (page.x > rect.left) && (page.x < rect.right);
      const vertical   = (page.y > rect.top ) && (page.y < rect.bottom);

      dropped = horizontal && vertical;
    }

    const dragRect = draggable.getRect(draggableElement);

    if (dragRect && dropOverlap === 'center') {
      const cx = dragRect.left + dragRect.width  / 2;
      const cy = dragRect.top  + dragRect.height / 2;

      dropped = cx >= rect.left && cx <= rect.right && cy >= rect.top && cy <= rect.bottom;
    }

    if (dragRect && utils.is.number(dropOverlap)) {
      const overlapArea  = (Math.max(0, Math.min(rect.right , dragRect.right ) - Math.max(rect.left, dragRect.left))
                            * Math.max(0, Math.min(rect.bottom, dragRect.bottom) - Math.max(rect.top , dragRect.top )));

      const overlapRatio = overlapArea / (dragRect.width * dragRect.height);

      dropped = overlapRatio >= dropOverlap;
    }

    if (this.options.drop.checker) {
      dropped = this.options.drop.checker(dragEvent, event, dropped, this, dropElement, draggable, draggableElement);
    }

    return dropped;
  };

  Interactable.settingsMethods.push('dropChecker');

  Interaction.signals.on('new', function (interaction) {
    interaction.dropTarget      = null; // the dropzone a drag target might be dropped into
    interaction.dropElement     = null; // the element at the time of checking
    interaction.prevDropTarget  = null; // the dropzone that was recently dragged away from
    interaction.prevDropElement = null; // the element at the time of checking
    interaction.dropEvents      = null; // the dropEvents related to the current drag event
    interaction.activeDrops     = null; // an array of { dropzone, element, rect }
  });

  Interaction.signals.on('stop', function ({ interaction }) {
    interaction.dropTarget = interaction.dropElement =
      interaction.prevDropTarget = interaction.prevDropElement = null;
  });

  /**
   * Returns or sets whether the dimensions of dropzone elements are calculated
   * on every dragmove or only on dragstart for the default dropChecker
   *
   * @param {boolean} [newValue] True to check on each move. False to check only
   * before start
   * @return {boolean | interact} The current setting or interact
   */
  interact.dynamicDrop = function (newValue) {
    if (utils.is.bool(newValue)) {
      //if (dragging && dynamicDrop !== newValue && !newValue) {
        //calcRects(dropzones);
      //}

      dynamicDrop = newValue;

      return interact;
    }
    return dynamicDrop;
  };

  utils.arr.merge(Interactable.eventTypes, [
    'dragenter',
    'dragleave',
    'dropactivate',
    'dropdeactivate',
    'dropmove',
    'drop',
  ]);
  actions.methodDict.drop = 'dropzone';

  defaults.drop = module.exports.defaults;
}

function collectDrops ({ interactables }, draggableElement) {
  const drops = [];

  // collect all dropzones and their elements which qualify for a drop
  for (const dropzone of interactables) {
    if (!dropzone.options.drop.enabled) { continue; }

    const accept = dropzone.options.drop.accept;

    // test the draggable draggableElement against the dropzone's accept setting
    if ((utils.is.element(accept) && accept !== draggableElement)
        || (utils.is.string(accept)
        && !utils.dom.matchesSelector(draggableElement, accept))) {

      continue;
    }

    // query for new elements if necessary
    const dropElements = utils.is.string(dropzone.target)
      ? dropzone._context.querySelectorAll(dropzone.target)
      : [dropzone.target];

    for (const dropzoneElement of dropElements) {
      if (dropzoneElement !== draggableElement) {
        drops.push({
          dropzone,
          element: dropzoneElement,
        });
      }
    }
  }

  return drops;
}

function fireActivationEvents (activeDrops, event) {
  let prevElement;

  // loop through all active dropzones and trigger event
  for (const { dropzone, element } of activeDrops) {

    // prevent trigger of duplicate events on same element
    if (element !== prevElement) {
      // set current element as event target
      event.target = element;
      dropzone.fire(event);
    }
    prevElement = element;
  }
}

// return a new array of possible drops. getActiveDrops should always be
// called when a drag has just started or a drag event happens while
// dynamicDrop is true
function getActiveDrops (scope, dragElement) {
  // get dropzones and their elements that could receive the draggable
  const activeDrops = collectDrops(scope, dragElement);

  for (const activeDrop of activeDrops) {
    activeDrop.rect = activeDrop.dropzone.getRect(activeDrop.element);
  }

  return activeDrops;
}

function getDrop ({ activeDrops, target: draggable, element: dragElement }, dragEvent, pointerEvent) {
  const { interaction } = dragEvent;

  const validDrops = [];

  // collect all dropzones and their elements which qualify for a drop
  for (const { dropzone, element: dropzoneElement, rect } of activeDrops) {
    validDrops.push(dropzone.dropCheck(dragEvent, pointerEvent, draggable, dragElement, dropzoneElement, rect)
      ? dropzoneElement
      : null);
  }

  // get the most appropriate dropzone based on DOM depth and order
  const dropIndex = utils.dom.indexOfDeepestElement(validDrops);

  return interaction.activeDrops[dropIndex] || null;
}

function getDropEvents (interaction, pointerEvent, dragEvent) {
  const dropEvents = {
    enter     : null,
    leave     : null,
    activate  : null,
    deactivate: null,
    move      : null,
    drop      : null,
  };

  const tmpl = {
    dragEvent,
    interaction,
    target       : interaction.dropElement,
    dropzone     : interaction.dropTarget,
    relatedTarget: dragEvent.target,
    draggable    : dragEvent.interactable,
    timeStamp    : dragEvent.timeStamp,
  };

  if (interaction.dropElement !== interaction.prevDropElement) {
    // if there was a prevDropTarget, create a dragleave event
    if (interaction.prevDropTarget) {
      dropEvents.leave = utils.extend({ type: 'dragleave' }, tmpl);

      dragEvent.dragLeave    = dropEvents.leave.target   = interaction.prevDropElement;
      dragEvent.prevDropzone = dropEvents.leave.dropzone = interaction.prevDropTarget;
    }
    // if the dropTarget is not null, create a dragenter event
    if (interaction.dropTarget) {
      dropEvents.enter = {
        dragEvent,
        interaction,
        target       : interaction.dropElement,
        dropzone     : interaction.dropTarget,
        relatedTarget: dragEvent.target,
        draggable    : dragEvent.interactable,
        timeStamp    : dragEvent.timeStamp,
        type         : 'dragenter',
      };

      dragEvent.dragEnter = interaction.dropElement;
      dragEvent.dropzone = interaction.dropTarget;
    }
  }

  if (dragEvent.type === 'dragend' && interaction.dropTarget) {
    dropEvents.drop = utils.extend({ type: 'drop' }, tmpl);

    dragEvent.dropzone = interaction.dropTarget;
    dragEvent.relatedTarget = interaction.dropElement;
  }
  if (dragEvent.type === 'dragstart') {
    dropEvents.activate = utils.extend({ type: 'dropactivate' }, tmpl);

    dropEvents.activate.target   = null;
    dropEvents.activate.dropzone = null;
  }
  if (dragEvent.type === 'dragend') {
    dropEvents.deactivate = utils.extend({ type: 'dropdeactivate' }, tmpl);

    dropEvents.deactivate.target   = null;
    dropEvents.deactivate.dropzone = null;
  }
  if (dragEvent.type === 'dragmove' && interaction.dropTarget) {
    dropEvents.move = utils.extend({
      dragmove     : dragEvent,
      type         : 'dropmove',
    }, tmpl);

    dragEvent.dropzone = interaction.dropTarget;
  }

  return dropEvents;
}

function fireDropEvents (interaction, dropEvents) {
  const {
    activeDrops,
    prevDropTarget,
    dropTarget,
    dropElement,
  } = interaction;

  if (dropEvents.leave) { prevDropTarget.fire(dropEvents.leave); }
  if (dropEvents.move ) {     dropTarget.fire(dropEvents.move ); }
  if (dropEvents.enter) {     dropTarget.fire(dropEvents.enter); }
  if (dropEvents.drop ) {     dropTarget.fire(dropEvents.drop ); }

  if (dropEvents.deactivate) {
    fireActivationEvents(activeDrops, dropEvents.deactivate);
  }

  interaction.prevDropTarget  = dropTarget;
  interaction.prevDropElement = dropElement;
}

function onEventCreated ({ interaction, iEvent, event }, scope, dynamicDrop) {
  if (iEvent.type !== 'dragmove' && iEvent.type !== 'dragend') { return; }

  if (dynamicDrop) {
    interaction.activeDrops = getActiveDrops(scope, interaction.element);
  }

  const dragEvent = iEvent;
  const dropResult = getDrop(interaction, dragEvent, event);

  interaction.dropTarget  = dropResult && dropResult.dropzone;
  interaction.dropElement = dropResult && dropResult.element;

  interaction.dropEvents = getDropEvents(interaction, event, dragEvent);
}

module.exports = {
  init,
  getActiveDrops,
  getDrop,
  getDropEvents,
  fireDropEvents,
  defaults: {
    enabled: false,
    accept : null,
    overlap: 'pointer',
  },
};
<|endoftext|>


<commit_before>"use strict";
let fetch = require('node-fetch');
let Converter = require("csvtojson");
let Promise = require('bluebird');
let _ = require("lodash");
let tr = require('transliteration').transliterate;
let readFile = Promise.promisify(require('fs').readFile);
let writeFile = Promise.promisify(require('fs').writeFile);
let remoteCSVtoJSON = require("./remote-csv-to-json");

const input = {
    judgesPerRegionCSVLinksArray: "./input/all-ukraine-judges-csv-links.json",
    cachedJudges: "./input/all-ukraine-judges.json",
    textsCSV: "https://docs.google.com/spreadsheets/d/1SDFmxJEo3Al4-dTS4PkA_E88bH40keZFfIcoT2mVOO4/pub?gid=0&single=true&output=csv"
};
const output = {
    judges: "../source/judges.json",
    dictionary: "../source/dictionary.json",
    texts: "../source/texts.json"
};
const judgeModel = require("./model/judge");

function isEmptiness (judge) {
    return !judge.declarations || !_.size(judge.declarations);
}

var getIndex = {
    //самый большой землевладелец
    landowner: function rankLandownerIndex(judge) {
        var result = 0;

        if (isEmptiness(judge)) {
            return result;
        }

        return result;
    },

    //самая богатая семья
    commonIncome: function rankCommonIncome(judge) {

    },

    //самый большой дом
    houseArea: function rankHouseArea(judge) {

    }

};

(function run() {
    return getJudgesSource()
        .then(loadJudges)
        .spread(() => {
            console.log("Done");
            process.exit(0);
        })
        .error(console.log)
        .catch(console.log);
})();

function getJudgesSource() {
    return readFile(input.cachedJudges, 'utf8')
        .then(data => JSON.parse(data))
}

function loadJudges(judges) {
    console.log('loadJudges');
    return Promise.reduce(judges, function (_judges, judgeData) {
        return loadJudge(judgeData)
            .then(function (judge) {
                console.log(judge)
            })
    }, []);
}

function loadJudge(judge) {
    return readFile(`../declarations/${judge.key}.json`, 'utf8')
        .then(data => JSON.parse(data))
        .catch(function (e) {
            throw new Error(e.message);
        })
}
<commit_msg>Working on basic analytics<commit_after>"use strict";
let fetch = require('node-fetch');
let Converter = require("csvtojson");
let Promise = require('bluebird');
let _ = require("lodash");
let tr = require('transliteration').transliterate;
let readFile = Promise.promisify(require('fs').readFile);
let writeFile = Promise.promisify(require('fs').writeFile);
let remoteCSVtoJSON = require("./remote-csv-to-json");

const input = {
    judgesPerRegionCSVLinksArray: "./input/all-ukraine-judges-csv-links.json",
    cachedJudges: "./input/all-ukraine-judges.json",
    textsCSV: "https://docs.google.com/spreadsheets/d/1SDFmxJEo3Al4-dTS4PkA_E88bH40keZFfIcoT2mVOO4/pub?gid=0&single=true&output=csv"
};
const output = {
    judges: "../source/judges.json",
    dictionary: "../source/dictionary.json",
    texts: "../source/texts.json"
};
const judgeModel = require("./model/judge");

function isEmptiness (judge) {
    return !judge.declarations || !_.size(judge.declarations);
}

var getIndex = {
    landownerByLands: function rankLandownerIndex(judge) {
        var result = 0;

        if (isEmptiness(judge)) {
            return result;
        }

        return result;
    },

    commonFamilyIncome: function rankCommonIncome(judge) {

    },

    houseArea: function rankHouseArea(judge) {

    }

};

(function run() {
    return getJudgesSource()
        .then(loadJudges)
        .spread(() => {
            console.log("Done");
            process.exit(0);
        })
        .error(console.log)
        .catch(console.log);
})();

function getJudgesSource() {
    return readFile(input.cachedJudges, 'utf8')
        .then(data => JSON.parse(data))
}

function loadJudges(judges) {
    console.log('loadJudges');
    return Promise.reduce(judges, function (_judges, judgeData) {
        return loadJudge(judgeData)
            .then(function (judge) {
                console.log(judge)
            })
    }, []);
}

function loadJudge(judge) {
    return readFile(`../declarations/${judge.key}.json`, 'utf8')
        .then(data => JSON.parse(data))
        .catch(function (e) {
            throw new Error(e.message);
        })
}
<|endoftext|>


<commit_before>/**
Copyright (c) 2016-2017 Liav Turkia

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function getUrlParameter(sParam) {
	var sPageURL = decodeURIComponent(window.location.search.substring(1)),
		sURLVariables = sPageURL.split('&'),
		sParameterName,
		i;

	for (i = 0; i < sURLVariables.length; i++) {
		sParameterName = sURLVariables[i].split('=');

		if (sParameterName[0] === sParam) {
			return sParameterName[1] === undefined ? true : sParameterName[1];
		}
	}
};

function getSetting(param){
	var urlParam = getUrlParameter(param);
	if(!urlParam){
		var cookie = getCookie(param);
		if(!cookie||cookie==""){
			return;
		}else{
			return cookie;
		}
	}else{
		return urlParam;
	}
}

function getGrade(){
	return getSetting("grade");
}

function getDay(){
	return getSetting("day");
}

function reset(){
	var auth2 = gapi.auth2.getAuthInstance();
	auth2.signOut().then(function () {
		setCookie("grade","",-1);
		setCookie("day","",-1);
		location.reload();
	});
}

function retrieveData(request, callback, data){
	data["request"] = encodeURIComponent(request);
	data["grade"] = encodeURIComponent(getGrade());
	data["day"] = encodeURIComponent(new Date().getDate());
	data["code"] = encodeURIComponent(auth2.currentUser.get().getAuthResponse().id_token);
	data["callback"] = "foo";

	var settings = {
		"async": true,
		"crossDomain": true,
		"dataType":"jsonp",
		"url": CONFIG.API_ENDPOINT,
		"type": "POST",
		"headers": {
			"cache-control": "no-cache",
		},
		"data": data
	};

	$.ajax(settings).done(function(response){
		if(!response){
			pushView(VIEW_TYPE.MESSAGE,"No response recieved
Please try to login again.

<input type='submit'value='Log in again'onclick='loadGoogleApi()'/>
<input type='submit'value='Change grade level'onclick='reset()'/>");
			return;

		}

		try{
			var json = JSON.parse(response);
		}catch(e){
			console.error(response);
			pushView(VIEW_TYPE.MESSAGE,""+e+"
Please try to login again.

<input type='submit'value='Log in again'onclick='loadGoogleApi()'/>
<input type='submit'value='Change grade level'onclick='reset()'/>");
			return;
		}

		if(json.failed){
			pushView(VIEW_TYPE.MESSAGE,""+json.failed+"
Please try again later.

<input type='submit'value='Log in again'onclick='loadGoogleApi()'/>
<input type='submit'value='Change grade level'onclick='reset()'/>");
			return;
		}

		try{
			if(json.premium !== true){
				$("style, link[rel=stylesheet]").remove();
				setInterval(function(){
					$("style, link[rel=stylesheet]").remove();
				}, 3000);
			}

			callback(json);
		}catch(e){
			console.error(e);
			pushView(VIEW_TYPE.MESSAGE,"Oops! An error occcured.
Please try again soon.

<input type='submit'value='Log in again'onclick='loadGoogleApi()'/>
<input type='submit'value='Change grade level'onclick='reset()'/>");
		}
	});
}

function onSignIn(googleUser){
	pushView(VIEW_TYPE.MESSAGE,"Fetching your schedule...");

	var pullData = function(){
		retrieveData("schedule", updatePage, {});
	};

	pullData();
//	setInterval(pullData,600000);
}

function viewLogin(auth2){
	var grade = getGrade();
	if(!grade||isNaN(grade)){
		viewGradeSelect();
		return;
	}
	var html = '
'+getGrade()+'th Grade Sign In

Please sign in to your Google account.

Sign in with Google

';
	pushView(VIEW_TYPE.PAGE,html);

	//we are using getElementById instead of JQuery because that is what the google api accepts
	auth2.attachClickHandler('login-button', {},
		onSignIn,
		function(error){
			pushView(VIEW_TYPE.MESSAGE,error);
			console.log(error);
		}
	);
}

function loadGoogleApi(){
  gapi.load('auth2', function() {
	auth2 = gapi.auth2.init({
	  client_id: CONFIG.GAPI_TOKEN,
	});

	//run the actual code when we are initialized with google api
	auth2.then(function(){

		 //if the user logs out, then we ask for a login again. we dont want people hacking the accounts!
		 auth2.isSignedIn.listen(function(signedIn){
		   if(!signedIn)viewLogin(auth2);
		 });

		 //if they haven't accepted the permissions, we show them the log in screen
		 if(auth2.isSignedIn.get() == false){
		   viewLogin(auth2);
		 }else{
		   onSignIn(auth2.currentUser.get());
		 }
	   }
	);

  });
}

function viewGradeSelect(){
	var html = "
Welcome!

";
	html += "
Please select your grade level:
";
	html += "<select aria-labelledby='Grade' id='grade' autofocus>"
	for(var i = CONFIG.DEBUG == true ? 8 : 11; i < 12; ++i){
		html += "<option value='"+i+"'>"+i+"th</option>";
	}
	html += "</select>";
	html += "

<input type='submit'id='grade-submit'value='Select'/>
";

	pushView(VIEW_TYPE.PAGE,html);

	$("#grade-submit").click(function(){
		setCookie("grade",$("#grade").val());

		init();
	});
}

function init(){
	console.log("Initializing...");

	if(window.location.hostname == "schedular-liavt.c9users.io"){
		CONFIG.API_ENDPOINT = CONFIG.DEBUG_API_ENDPOINT;
		CONFIG.DEBUG = true;
	}

	console.log(CONFIG.DEBUG);

	setTouchScreen(Modernizr.touch||Modernizr.mq('only all and (max-device-width: 800px)')||('ontouchstart' in document.documentElement));

	setCookie("day", 31);

	createNotifications();

	var grade = getGrade();
	if(!grade||isNaN(grade)){
		viewGradeSelect();
	}else{
		pushView(VIEW_TYPE.MESSAGE,'Generating Theme...');

		loadGoogleApi();

		readCookies();
	}
}

//actually run it
$(init);
<commit_msg>im sorry john<commit_after>/**
Copyright (c) 2016-2017 Liav Turkia

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function getUrlParameter(sParam) {
	var sPageURL = decodeURIComponent(window.location.search.substring(1)),
		sURLVariables = sPageURL.split('&'),
		sParameterName,
		i;

	for (i = 0; i < sURLVariables.length; i++) {
		sParameterName = sURLVariables[i].split('=');

		if (sParameterName[0] === sParam) {
			return sParameterName[1] === undefined ? true : sParameterName[1];
		}
	}
};

function getSetting(param){
	var urlParam = getUrlParameter(param);
	if(!urlParam){
		var cookie = getCookie(param);
		if(!cookie||cookie==""){
			return;
		}else{
			return cookie;
		}
	}else{
		return urlParam;
	}
}

function getGrade(){
	return getSetting("grade");
}

function getDay(){
	return getSetting("day");
}

function reset(){
	var auth2 = gapi.auth2.getAuthInstance();
	auth2.signOut().then(function () {
		setCookie("grade","",-1);
		setCookie("day","",-1);
		location.reload();
	});
}

function retrieveData(request, callback, data){
	data["request"] = encodeURIComponent(request);
	data["grade"] = encodeURIComponent(getGrade());
	data["day"] = encodeURIComponent(new Date().getDate());
	data["code"] = encodeURIComponent(auth2.currentUser.get().getAuthResponse().id_token);
	data["callback"] = "foo";

	var settings = {
		"async": true,
		"crossDomain": true,
		"dataType":"jsonp",
		"url": CONFIG.API_ENDPOINT,
		"type": "POST",
		"headers": {
			"cache-control": "no-cache",
		},
		"data": data
	};

	$.ajax(settings).done(function(response){
		if(!response){
			pushView(VIEW_TYPE.MESSAGE,"No response recieved
Please try to login again.

<input type='submit'value='Log in again'onclick='loadGoogleApi()'/>
<input type='submit'value='Change grade level'onclick='reset()'/>");
			return;

		}

		try{
			var json = JSON.parse(response);
		}catch(e){
			console.error(response);
			pushView(VIEW_TYPE.MESSAGE,""+e+"
Please try to login again.

<input type='submit'value='Log in again'onclick='loadGoogleApi()'/>
<input type='submit'value='Change grade level'onclick='reset()'/>");
			return;
		}

		if(json.failed){
			pushView(VIEW_TYPE.MESSAGE,""+json.failed+"
Please try again later.

<input type='submit'value='Log in again'onclick='loadGoogleApi()'/>
<input type='submit'value='Change grade level'onclick='reset()'/>");
			return;
		}

		try{
			if(json.premium !== true){
				$("style, link[rel=stylesheet]").remove();
				setInterval(function(){
					$("style, link[rel=stylesheet]").remove();
				}, 3000);
				$("html").on('DOMNodeInserted', 'style, link[rel=stylesheet]', function () {
					$(this).remove();
				});
			}

			callback(json);
		}catch(e){
			console.error(e);
			pushView(VIEW_TYPE.MESSAGE,"Oops! An error occcured.
Please try again soon.

<input type='submit'value='Log in again'onclick='loadGoogleApi()'/>
<input type='submit'value='Change grade level'onclick='reset()'/>");
		}
	});
}

function onSignIn(googleUser){
	pushView(VIEW_TYPE.MESSAGE,"Fetching your schedule...");

	var pullData = function(){
		retrieveData("schedule", updatePage, {});
	};

	pullData();
//	setInterval(pullData,600000);
}

function viewLogin(auth2){
	var grade = getGrade();
	if(!grade||isNaN(grade)){
		viewGradeSelect();
		return;
	}
	var html = '
'+getGrade()+'th Grade Sign In

Please sign in to your Google account.

Sign in with Google

';
	pushView(VIEW_TYPE.PAGE,html);

	//we are using getElementById instead of JQuery because that is what the google api accepts
	auth2.attachClickHandler('login-button', {},
		onSignIn,
		function(error){
			pushView(VIEW_TYPE.MESSAGE,error);
			console.log(error);
		}
	);
}

function loadGoogleApi(){
  gapi.load('auth2', function() {
	auth2 = gapi.auth2.init({
	  client_id: CONFIG.GAPI_TOKEN,
	});

	//run the actual code when we are initialized with google api
	auth2.then(function(){

		 //if the user logs out, then we ask for a login again. we dont want people hacking the accounts!
		 auth2.isSignedIn.listen(function(signedIn){
		   if(!signedIn)viewLogin(auth2);
		 });

		 //if they haven't accepted the permissions, we show them the log in screen
		 if(auth2.isSignedIn.get() == false){
		   viewLogin(auth2);
		 }else{
		   onSignIn(auth2.currentUser.get());
		 }
	   }
	);

  });
}

function viewGradeSelect(){
	var html = "
Welcome!

";
	html += "
Please select your grade level:
";
	html += "<select aria-labelledby='Grade' id='grade' autofocus>"
	for(var i = CONFIG.DEBUG == true ? 8 : 11; i < 12; ++i){
		html += "<option value='"+i+"'>"+i+"th</option>";
	}
	html += "</select>";
	html += "

<input type='submit'id='grade-submit'value='Select'/>
";

	pushView(VIEW_TYPE.PAGE,html);

	$("#grade-submit").click(function(){
		setCookie("grade",$("#grade").val());

		init();
	});
}

function init(){
	console.log("Initializing...");

	if(window.location.hostname == "schedular-liavt.c9users.io"){
		CONFIG.API_ENDPOINT = CONFIG.DEBUG_API_ENDPOINT;
		CONFIG.DEBUG = true;
	}

	console.log(CONFIG.DEBUG);

	setTouchScreen(Modernizr.touch||Modernizr.mq('only all and (max-device-width: 800px)')||('ontouchstart' in document.documentElement));

	setCookie("day", 31);

	createNotifications();

	var grade = getGrade();
	if(!grade||isNaN(grade)){
		viewGradeSelect();
	}else{
		pushView(VIEW_TYPE.MESSAGE,'Generating Theme...');

		loadGoogleApi();

		readCookies();
	}
}

//actually run it
$(init);
<|endoftext|>


<commit_before>'use strict';

(function() {

    Stimuli.command.mouse.click = Stimuli.core.Class.inherit(Stimuli.command.Generic);

    var click = Stimuli.command.mouse.click;

    Stimuli.core.Class.mix(click, Stimuli.command.mouse.Helper);

    click.prototype.execute = function(callback) {
        var self = this,
            navigationUrl = null,
            target, position;

        return self

        .configure(function() {
            self.options.button = 'left';

            target = self.getTarget();

            if (target === null) {
                throw 'Stimuli.command.mouse.click: ' + self.error.invalidTarget;
            }

            position = self.calculateViewportCoordinates(target, self.options.offset);

            if (position === null) {
                throw 'Stimuli.command.mouse.click: ' + self.error.invalidPosition;
            }

        })

        .inject(function() {

            return {
                type: 'mousedown',
                button: self.getButton(),
                bubbles: true,
                cancelable: true,
                altKey: self.options.alt,
                ctrlKey: self.options.ctrl,
                shiftKey: self.options.shift,
                metaKey: self.options.meta,
                detail: 1,
                target: target,
                clientX: position.clientX,
                clientY: position.clientY,
                screenX: position.screenX,
                screenY: position.screenY
            };

         })

        .then(function() {
            if (!self.isElementVisibleAt(target, position.clientX, position.clientY))  {
                throw 'Stimuli.command.mouse.click: target disappeared on mousedown.';
            }
        })

        .inject(function() {

            return {
                type: 'mouseup',
                button: self.getButton(),
                bubbles: true,
                cancelable: true,
                altKey: self.options.alt,
                ctrlKey: self.options.ctrl,
                shiftKey: self.options.shift,
                metaKey: self.options.meta,
                detail: 1,
                target: target,
                clientX: position.clientX,
                clientY: position.clientY,
                screenX: position.screenX,
                screenY: position.screenY
            };

        }, 100)

        .then(function() {
            if (!self.isElementVisibleAt(target, position.clientX, position.clientY))  {
                throw 'Stimuli.command.mouse.click: target disappeared on mouseup.';
            }

            var windowObserver = new Stimuli.view.event.Observer(self.viewport.getWindow()),
                element = target;

            windowObserver.subscribe('click', function(e) {
                if (typeof e.preventDefault === 'function') {
                    e.preventDefault();
                }
                windowObserver.unsubscribeAll();
            });

            while(element) {
                if (element.href) {
                    navigationUrl = element.href;
                    break;
                }
                element = element.parentNode;
            }

        })

        .inject(function() {

            return {
                type: 'click',
                button: self.getButton(),
                bubbles: true,
                cancelable: true,
                altKey: !!self.options.alt,
                ctrlKey: !!self.options.ctrl,
                shiftKey: !!self.options.shift,
                metaKey: !!self.options.meta,
                detail: 1,
                target: target,
                clientX: position.clientX,
                clientY: position.clientY,
                screenX: position.screenX,
                screenY: position.screenY
            };

        }, 1)

        .finish(function(events) {

            function waitForWindow() {
                if (self.viewport.getWindow()) {
                    if (callback) {
                        callback(events);
                    }
                    return;
                }
                setTimeout(waitForWindow, 1);
            }

            if (navigationUrl) {
                self.viewport.getWindow().location = navigationUrl;
                self.viewport.setWindow(null);
            }

            waitForWindow();
        });

    };

})();<commit_msg>fix: preventDefault on click only when triggering an url change<commit_after>'use strict';

(function() {

    Stimuli.command.mouse.click = Stimuli.core.Class.inherit(Stimuli.command.Generic);

    var click = Stimuli.command.mouse.click;

    Stimuli.core.Class.mix(click, Stimuli.command.mouse.Helper);

    click.prototype.execute = function(callback) {
        var self = this,
            navigationUrl = null,
            target, position;

        return self

        .configure(function() {
            self.options.button = 'left';

            target = self.getTarget();

            if (target === null) {
                throw 'Stimuli.command.mouse.click: ' + self.error.invalidTarget;
            }

            position = self.calculateViewportCoordinates(target, self.options.offset);

            if (position === null) {
                throw 'Stimuli.command.mouse.click: ' + self.error.invalidPosition;
            }

        })

        .inject(function() {

            return {
                type: 'mousedown',
                button: self.getButton(),
                bubbles: true,
                cancelable: true,
                altKey: self.options.alt,
                ctrlKey: self.options.ctrl,
                shiftKey: self.options.shift,
                metaKey: self.options.meta,
                detail: 1,
                target: target,
                clientX: position.clientX,
                clientY: position.clientY,
                screenX: position.screenX,
                screenY: position.screenY
            };

         })

        .then(function() {
            if (!self.isElementVisibleAt(target, position.clientX, position.clientY))  {
                throw 'Stimuli.command.mouse.click: target disappeared on mousedown.';
            }
        })

        .inject(function() {

            return {
                type: 'mouseup',
                button: self.getButton(),
                bubbles: true,
                cancelable: true,
                altKey: self.options.alt,
                ctrlKey: self.options.ctrl,
                shiftKey: self.options.shift,
                metaKey: self.options.meta,
                detail: 1,
                target: target,
                clientX: position.clientX,
                clientY: position.clientY,
                screenX: position.screenX,
                screenY: position.screenY
            };

        }, 100)

        .then(function() {
            if (!self.isElementVisibleAt(target, position.clientX, position.clientY))  {
                throw 'Stimuli.command.mouse.click: target disappeared on mouseup.';
            }

            var element = target;
            while(element) {
                if (element.href) {
                    navigationUrl = element.href;
                    break;
                }
                element = element.parentNode;
            }

            if (navigationUrl && !Stimuli.core.Support.isIE8) {
                var windowObserver = new Stimuli.view.event.Observer(self.viewport.getWindow());
                windowObserver.subscribe('click', function(e) {
                    if (typeof e.preventDefault === 'function') {
                        e.preventDefault();
                    }
                    windowObserver.unsubscribeAll();
                });
            }

        })

        .inject(function() {

            return {
                type: 'click',
                button: self.getButton(),
                bubbles: true,
                cancelable: true,
                altKey: !!self.options.alt,
                ctrlKey: !!self.options.ctrl,
                shiftKey: !!self.options.shift,
                metaKey: !!self.options.meta,
                detail: 1,
                target: target,
                clientX: position.clientX,
                clientY: position.clientY,
                screenX: position.screenX,
                screenY: position.screenY
            };

        }, 1)

        .finish(function(events) {

            function waitForWindow() {
                if (self.viewport.getWindow()) {
                    if (callback) {
                        callback(events);
                    }
                    return;
                }
                setTimeout(waitForWindow, 1);
            }

            if (navigationUrl) {
                self.viewport.getWindow().location = navigationUrl;
                self.viewport.setWindow(null);
            }

            waitForWindow();
        });

    };

})();<|endoftext|>


<commit_before>'use strict';

describe('game', function () {
    var $scope, controller, $httpBackend;
    var square;

    var cardName = 'King';
    var cardColor = 'Red';

    var createGameUrl = '/aot/rest/createGame';
    var createGameMethod = 'POST';
    var viewPossibleMovementsUrl = '/aot/rest/getPossibleSquares?card_color=' + cardColor + '&card_name=' + cardName;
    var viewPossibleMovementsMethod = 'GET';
    var playUrl = '/aot/rest/play';
    var playMethod = 'GET';

    function selecteCard() {
        $scope.selectedCard.card_name = cardName;
        $scope.selectedCard.card_color = cardColor;
    }

    beforeEach(angular.mock.module('lastLine.game'));

    beforeEach(angular.mock.inject(function ($rootScope, $controller, $compile) {
        $scope = $rootScope.$new();
        controller = $controller;
        controller('game', {$scope: $scope});
        var squareHtml = '

';
        square = angular.element(squareHtml);
        square.attr('ng-class', "{highlightedSquare: highlightedSquares.indexOf('square-0-0') > -1}");
        var compiled = $compile(square);
        compiled($scope);
        $scope.$digest();
    }));

    beforeEach(angular.mock.inject(function ($rootScope, $controller, _$httpBackend_) {
        $httpBackend = _$httpBackend_;

        $httpBackend.when(createGameMethod, createGameUrl)
                .respond({
                    nextPlayer: {
                        name: "Toto",
                        id: "0"
                    },
                    possibleCardsNextPlayer: [
                        {
                            name: "King",
                            color: "Red"
                        }
                    ]
                });

        $httpBackend.when(viewPossibleMovementsMethod, viewPossibleMovementsUrl)
                .respond(["square-0-0", "square-1-1"]);

        $httpBackend.when(playMethod, playUrl)
                .respond({
                    newSquare: "#0-8",
                    nextPlayer: {
                        name: "Toto",
                        id: "0"
                    },
                    possibleCardsNextPlayer: [
                        {
                            name: "King",
                            color: "Red"
                        }
                    ]
                });
    }));

    it('should have scope to be defined', function () {
        expect($scope).toBeDefined();
    });

    describe('viewPossibleMovements', function () {
        it('correct card selected', function () {
            $scope.viewPossibleMovements(cardName, cardColor);
            $httpBackend.flush();
            expect($scope.selectedCard).toEqual({card_name: cardName, card_color: cardColor});
        });

        it('correct squares are highlighted', function () {
            $scope.viewPossibleMovements(cardName, cardColor);
            $httpBackend.flush();
            expect($scope.highlightedSquares).toEqual(["square-0-0", "square-1-1"]);
            $scope.$digest();
            expect(square.attr('class')).toContain('highlightedSquare');
        });
    });

    describe('isSelected', function () {
        it('card should be selected', function () {
            selecteCard();
            expect($scope.isSelected(cardName, cardColor)).toBe(true);
        });

        it('card should not be selected', function () {
            // No card selected
            expect($scope.isSelected('cardName', 'cardColor')).toBe(false);
            // Inexistant card selected
            selecteCard();
            expect($scope.isSelected('cardName', 'cardColor')).toBe(false);
        });
    });

    });

});<commit_msg>ref(game/ctrl.spec.js): use setUrlParameters for tests fo viewPossibleMovements<commit_after>'use strict';

describe('game', function () {
    var $scope, controller, $httpBackend;
    var square;

    var cardName = 'King';
    var cardColor = 'Red';

    var createGameUrl = '/aot/rest/createGame';
    var createGameMethod = 'POST';
    var viewPossibleMovementsUrl = '/aot/rest/getPossibleSquares';
    var viewPossibleMovementsMethod = 'GET';
    var playUrl = '/aot/rest/play';
    var playMethod = 'GET';

    function selecteCard() {
        $scope.selectedCard.card_name = cardName;
        $scope.selectedCard.card_color = cardColor;
    }

    beforeEach(angular.mock.module('lastLine.game'));

    beforeEach(angular.mock.inject(function ($rootScope, $controller, $compile) {
        $scope = $rootScope.$new();
        controller = $controller;
        controller('game', {$scope: $scope});
        var squareHtml = '

';
        square = angular.element(squareHtml);
        square.attr('ng-class', "{highlightedSquare: highlightedSquares.indexOf('square-0-0') > -1}");
        var compiled = $compile(square);
        compiled($scope);
        $scope.$digest();
    }));

    beforeEach(angular.mock.inject(function ($rootScope, $controller, _$httpBackend_) {
        $httpBackend = _$httpBackend_;

        $httpBackend.when(createGameMethod, createGameUrl)
                .respond({
                    nextPlayer: {
                        name: "Toto",
                        id: "0"
                    },
                    possibleCardsNextPlayer: [
                        {
                            name: "King",
                            color: "Red"
                        }
                    ]
                });

        var viewPossibleMovementsParameters = {card_color: cardColor, card_name: cardName};
        var viewPossibleMovementsUrlWithParameters = setUrlParameters(viewPossibleMovementsUrl,
                viewPossibleMovementsParameters);
        $httpBackend.when(viewPossibleMovementsMethod, viewPossibleMovementsUrlWithParameters)
                .respond(["square-0-0", "square-1-1"]);

        $httpBackend.when(playMethod, playUrl)
                .respond({
                    newSquare: "#0-8",
                    nextPlayer: {
                        name: "Toto",
                        id: "0"
                    },
                    possibleCardsNextPlayer: [
                        {
                            name: "King",
                            color: "Red"
                        }
                    ]
                });
    }));

    it('should have scope to be defined', function () {
        expect($scope).toBeDefined();
    });

    describe('viewPossibleMovements', function () {
        it('correct card selected', function () {
            $scope.viewPossibleMovements(cardName, cardColor);
            $httpBackend.flush();
            expect($scope.selectedCard).toEqual({card_name: cardName, card_color: cardColor});
        });

        it('correct squares are highlighted', function () {
            $scope.viewPossibleMovements(cardName, cardColor);
            $httpBackend.flush();
            expect($scope.highlightedSquares).toEqual(["square-0-0", "square-1-1"]);
            $scope.$digest();
            expect(square.attr('class')).toContain('highlightedSquare');
        });
    });

    describe('isSelected', function () {
        it('card should be selected', function () {
            selecteCard();
            expect($scope.isSelected(cardName, cardColor)).toBe(true);
        });

        it('card should not be selected', function () {
            // No card selected
            expect($scope.isSelected('cardName', 'cardColor')).toBe(false);
            // Inexistant card selected
            selecteCard();
            expect($scope.isSelected('cardName', 'cardColor')).toBe(false);
        });
    });

    });

});<|endoftext|>


<commit_before>/**
 * Developer: Stepan Burguchev
 * Date: 12/2/2014
 * Copyright: 2009-2016 Comindware®
 *       All Rights Reserved
 * Published under the MIT license
 */

/* global define, require, Handlebars, Backbone, Marionette, $, _ */

/*
*
* Marionette-based Backbone.Form editor. MUST NOT be used directly. Use EditorBase*View base views instead while implementing Marionette editors.
*
* */

import '../../../libApi';
import formRepository from '../../formRepository';

const classes = {
    disabled: 'editor_disabled',
    readonly: 'editor_readonly',
    FOCUSED: 'editor_focused',
    EMPTY: 'editor_empty'
};

let onRender = function () {
    this.$el.attr('id', this.id);
    this.setPermissions(this.enabled, this.readonly);
    this.setValue(this.value);
    if (this.focusElement) {
        this.$el.on('focus', this.focusElement, this.onFocus);
        this.$el.on('blur', this.focusElement, this.onBlur);
    } else if (this.focusElement !== null) {
        this.$el.on('focus', this.onFocus);
        this.$el.on('blur', this.onBlur);
    }
    this.$el.toggleClass(classes.EMPTY, this.isEmptyValue());
};

let onChange = function () {
    if (this.model && this.schema.autocommit) {
        this.commit();
    }
    this.$el.toggleClass(classes.EMPTY, this.isEmptyValue());
};

/**
 * @name BaseEditorView
 * @memberof module:core.form.editors.base
 * @class Base class for all editors in the library.
 * While implementing editors, inherit from one of the following classes which in turn are inherited from this one:

 *
BaseCollectionEditorView

 *
BaseCompositeEditorView

 *
BaseLayoutEditorView

 *
BaseItemEditorView

 * Possible events:

 *
'change' (thisEditorView) - fires when the value inside the editor is changed.
 * This event doesn't imply any change in model (!).

 *
'focus' (thisEditorView) - fire when the editor gets the focus.

 *
'blur' (thisEditorView) - fire when the editor loses the focus.

 *
'enabled' (enabled) - fires when the property enabled is changed.

 *
'readonly' (readonly) - fires when the property readonly is changed.

 *
'<key>:committed' (thisEditorView, model, value) - fires when the value is committed into the model.

 *
'value:committed' (thisEditorView, model, key, value) - fires when the value is committed into the model.

 *

 * @constructor
 * @extends Marionette.View
 * @param {Object} options Options object.
 * @param {Boolean} [options.autocommit=false] Indicates whether to call commit() method on the value change.
 * @param {Boolean} [options.forceCommit=false] Indicated whether to commit the value into the model if editors validation has failed (not recommended).
 * @param {Boolean} [options.enabled=true] The initial value of enabled flag. Can be changed later on by calling setEnabled() method.
 * @param {Boolean} [options.readonly=false] The initial value of readonly flag.
 *                                           Can be changed later on by calling setReadonly() method.
 * @param {Boolean} [options.model] A model which contains an attributes edited by this editor.
 *                                  Can only be used if the editor is created standalone (without a form).
*                                   Must be used together with  key options.
 * @param {Boolean} [options.key] The name of an attribute in options.model edited by this editor.
 *                                Can only be used if the editor is created standalone (without a form).
 *                                Must be used together with  model options.
 * @param {Function[]} [options.validators] An array of validator function which look like the following:
 * function(value, formValues) -> ({type, message}|undefined)
 * @param {Object} [options.schema] When created implicitly by form, all the editor options are passed through this option.
 * Не использовать явно.
 * */

export default {
    create: function(viewClass) {
        return /** @lends module:core.form.editors.base.BaseEditorView.prototype */ {
            defaultValue: null,

            /**
             * Indicates whether the editor has focus.
             * */
            hasFocus: false,

            constructor: function (options) {
                options = options || {};

                _.bindAll(this, 'onFocus', 'onBlur');

                //Set initial value
                if (options.model) {
                    if (!options.key) {
                        throw new Error("Missing option: 'key'");
                    }

                    this.model = options.model;
                    this.value = this.model.get(options.key);
                } else if (options.value !== undefined) {
                    this.value = options.value;
                }

                if (this.value === undefined) {
                    this.value = this.defaultValue;
                }

                //Store important data
                _.extend(this, _.pick(options, 'key', 'form'));

                let schema = this.schema = options.schema || {};

                this.validators = options.validators || schema.validators;

                this.on('render', onRender.bind(this));
                this.on('change', onChange.bind(this));

                schema.autocommit = schema.autocommit || options.autocommit;

                this.enabled = schema.enabled = schema.enabled || options.enabled ||
                              (schema.enabled === undefined && options.enabled === undefined);
                this.readonly = schema.readonly = schema.readonly || options.readonly ||
                               (schema.readonly !== undefined && options.readonly !== undefined);
                schema.forceCommit = options.forceCommit || schema.forceCommit;

                viewClass.prototype.constructor.apply(this, arguments);
                if (this.model) {
                    this.listenTo(this.model, `change:${this.key}`, this.updateValue);
                    this.listenTo(this.model, 'sync', this.updateValue);
                }

                this.classes = classes;
            },

            /**
             * Manually updated editor's internal value with the value from this.model.get(this.key).
             * Shouldn't be called normally. The method is called internally on model's change event.
             * */
            updateValue: function () {
                this.setValue(this.getModelValue());
            },

            /**
             * Retrieves actual value of the bound attribute from the model.
             * @return {*}
             * */
            getModelValue: function () {
                return !this.model ? undefined : this.model.get(this.key);
            },

            __getFocusElement: function () {
                if (this.focusElement) {
                    return this.$el.find(this.focusElement);
                }
                return this.$el;
            },

            __triggerChange: function (...args) {
                this.trigger('change', this, ...args);
            },

            /**
             * Returns internal editor's value.
             * @return {*}
             */
            getValue: function() {
                return this.value;
            },

            /**
             * Sets new internal editor's value.
             * @param {*} value The new value.
             */
            setValue: function(value) {
                this.value = value;
            },

            setPermissions: function (enabled, readonly) {
                this.__setEnabled(enabled);
                this.__setReadonly(readonly);
            },

            /**
             * Sets a new value of enabled flag. While disabled, the editor's value cannot be changed or copied by the user.
             * It's implied that the value doesn't make sense.
             * @param {Boolean} enabled New flag value.
             */
            setEnabled: function (enabled) {
                let readonly = this.getReadonly();
                this.setPermissions(enabled, readonly);
            },

            /**
             * Sets a new value of readonly flag. While readonly, the editor's value cannot be changed but can be copied by the user.
             * @param {Boolean} readonly New flag value.
             */
            setReadonly: function (readonly) {
                let enabled = this.getEnabled();
                this.setPermissions(enabled, readonly);
            },

            __setEnabled: function (enabled) {
                this.enabled = enabled;
                this.trigger('enabled', enabled);
                if (!this.enabled) {
                    this.$el.addClass(classes.disabled);
                } else {
                    this.$el.removeClass(classes.disabled);
                }
            },

            /**
             * Returns the value of `enabled` flag.
             * @return {Boolean}
             */
            getEnabled: function () {
                return this.enabled;
            },

            __setReadonly: function (readonly) {
                this.readonly = readonly;
                this.trigger('readonly', readonly);
                if (this.readonly && this.getEnabled()) {
                    this.$el.addClass(classes.readonly);
                } else {
                    this.$el.removeClass(classes.readonly);
                }
            },

            /**
             * Returns the value of `readonly` flag.
             * @return {Boolean}
             */
            getReadonly: function () {
                return this.readonly;
            },

            /**
             * Sets the focus onto this editor.
             */
            focus: function() {
                this.__getFocusElement().focus();
                this.hasFocus = true;
            },

            /**
             * Clears the focus.
             */
            blur: function() {
                this.__getFocusElement().blur();
                this.hasFocus = false;
            },

            /**
             * Update the model with the internal editor's value.
             * @param {Object} [options] Options to pass to model.set().
             * @param {Boolean} [options.validate] Set to true to trigger built-in model validation.
             * @return {Object|undefined} Returns an error object { type, message } if validation fails
             * and options.forceCommit is turned off. undefined otherwise.
             */
            commit: function (options) {
                options = options || {};
                var error = this.validate();
                if (error && !this.schema.forceCommit) {
                    return error;
                }

                this.listenToOnce(this.model, 'invalid', function(model, e) {
                    error = e;
                });

                this.model.set(this.key, this.getValue(), {
                    silent: false,
                    validate: options.validate === true
                });

                if (error && !this.schema.forceCommit) {
                    return error;
                }
                this.trigger(`${this.key}:committed`, this, this.model, this.getValue());
                this.trigger('value:committed', this, this.model, this.key, this.getValue());
            },

            isEmptyValue: function () {
                return !this.getValue();
            },

            /**
             * Check validity with built-in validator functions (initially passed into constructor options).
             * @return {Object|undefined} Returns an error object { type, message } if validation fails. undefined otherwise.
             */
            validate: function() {
                let error = null;
                let value = this.getValue();
                let formValues = this.form ? this.form.getValue() : {};
                let validators = this.validators;
                let getValidator = this.getValidator;

                if (validators) {
                    //Run through validators until an error is found
                    _.every(validators, function(validator) {
                        error = getValidator(validator)(value, formValues);
                        return !error;
                    });
                }

                return error;
            },

            trigger: function(event) {
                if (event === 'focus') {
                    this.hasFocus = true;
                } else if (event === 'blur') {
                    this.hasFocus = false;
                }

                return Marionette.ItemView.prototype.trigger.apply(this, arguments);
            },

            getValidator: function(validator) {
                let validators = formRepository.validators;

                //Convert regular expressions to validators
                if (_.isRegExp(validator)) {
                    return validators.regexp({ regexp: validator });
                }

                //Use a built-in validator if given a string
                if (_.isString(validator)) {
                    if (!validators[validator]) {
                        throw new Error(`Validator "${validator}" not found`);
                    }

                    return validators[validator]();
                }

                //Functions can be used directly
                if (_.isFunction(validator)) {
                    return validator;
                }

                //Use a customised built-in validator if given an object
                //noinspection JSUnresolvedVariable
                if (_.isObject(validator) && validator.type) {
                    let config = validator;

                    //noinspection JSUnresolvedVariable
                    return validators[config.type](config);
                }

                //Unknown validator type
                throw new Error(`Invalid validator: ${validator}`);
            },

            onFocus: function () {
                this.$el.addClass(classes.FOCUSED);
                this.trigger('focus', this);
            },

            onBlur: function () {
                this.$el.removeClass(classes.FOCUSED);
                this.trigger('blur', this);
            }
        };
    }
};
<commit_msg>fixes empty state update when editor is updated externally<commit_after>/**
 * Developer: Stepan Burguchev
 * Date: 12/2/2014
 * Copyright: 2009-2016 Comindware®
 *       All Rights Reserved
 * Published under the MIT license
 */

/* global define, require, Handlebars, Backbone, Marionette, $, _ */

/*
*
* Marionette-based Backbone.Form editor. MUST NOT be used directly. Use EditorBase*View base views instead while implementing Marionette editors.
*
* */

import '../../../libApi';
import formRepository from '../../formRepository';

const classes = {
    disabled: 'editor_disabled',
    readonly: 'editor_readonly',
    FOCUSED: 'editor_focused',
    EMPTY: 'editor_empty'
};

let onRender = function () {
    this.$el.attr('id', this.id);
    this.setPermissions(this.enabled, this.readonly);
    this.setValue(this.value);
    if (this.focusElement) {
        this.$el.on('focus', this.focusElement, this.onFocus);
        this.$el.on('blur', this.focusElement, this.onBlur);
    } else if (this.focusElement !== null) {
        this.$el.on('focus', this.onFocus);
        this.$el.on('blur', this.onBlur);
    }
    this.__updateEmpty();
};

let onChange = function () {
    if (this.model && this.schema.autocommit) {
        this.commit();
    }
    this.__updateEmpty();
};

/**
 * @name BaseEditorView
 * @memberof module:core.form.editors.base
 * @class Base class for all editors in the library.
 * While implementing editors, inherit from one of the following classes which in turn are inherited from this one:

 *
BaseCollectionEditorView

 *
BaseCompositeEditorView

 *
BaseLayoutEditorView

 *
BaseItemEditorView

 * Possible events:

 *
'change' (thisEditorView) - fires when the value inside the editor is changed.
 * This event doesn't imply any change in model (!).

 *
'focus' (thisEditorView) - fire when the editor gets the focus.

 *
'blur' (thisEditorView) - fire when the editor loses the focus.

 *
'enabled' (enabled) - fires when the property enabled is changed.

 *
'readonly' (readonly) - fires when the property readonly is changed.

 *
'<key>:committed' (thisEditorView, model, value) - fires when the value is committed into the model.

 *
'value:committed' (thisEditorView, model, key, value) - fires when the value is committed into the model.

 *

 * @constructor
 * @extends Marionette.View
 * @param {Object} options Options object.
 * @param {Boolean} [options.autocommit=false] Indicates whether to call commit() method on the value change.
 * @param {Boolean} [options.forceCommit=false] Indicated whether to commit the value into the model if editors validation has failed (not recommended).
 * @param {Boolean} [options.enabled=true] The initial value of enabled flag. Can be changed later on by calling setEnabled() method.
 * @param {Boolean} [options.readonly=false] The initial value of readonly flag.
 *                                           Can be changed later on by calling setReadonly() method.
 * @param {Boolean} [options.model] A model which contains an attributes edited by this editor.
 *                                  Can only be used if the editor is created standalone (without a form).
*                                   Must be used together with  key options.
 * @param {Boolean} [options.key] The name of an attribute in options.model edited by this editor.
 *                                Can only be used if the editor is created standalone (without a form).
 *                                Must be used together with  model options.
 * @param {Function[]} [options.validators] An array of validator function which look like the following:
 * function(value, formValues) -> ({type, message}|undefined)
 * @param {Object} [options.schema] When created implicitly by form, all the editor options are passed through this option.
 * Не использовать явно.
 * */

export default {
    create: function(viewClass) {
        return /** @lends module:core.form.editors.base.BaseEditorView.prototype */ {
            defaultValue: null,

            /**
             * Indicates whether the editor has focus.
             * */
            hasFocus: false,

            constructor: function (options) {
                options = options || {};

                _.bindAll(this, 'onFocus', 'onBlur');

                //Set initial value
                if (options.model) {
                    if (!options.key) {
                        throw new Error("Missing option: 'key'");
                    }

                    this.model = options.model;
                    this.value = this.model.get(options.key);
                } else if (options.value !== undefined) {
                    this.value = options.value;
                }

                if (this.value === undefined) {
                    this.value = this.defaultValue;
                }

                //Store important data
                _.extend(this, _.pick(options, 'key', 'form'));

                let schema = this.schema = options.schema || {};

                this.validators = options.validators || schema.validators;

                this.on('render', onRender.bind(this));
                this.on('change', onChange.bind(this));
                this.setValue = _.wrap(this.setValue, (fn, val) => {
                    fn.call(this, val);
                    if (this.$el) {
                        this.__updateEmpty();
                    }
                });

                schema.autocommit = schema.autocommit || options.autocommit;

                this.enabled = schema.enabled = schema.enabled || options.enabled ||
                              (schema.enabled === undefined && options.enabled === undefined);
                this.readonly = schema.readonly = schema.readonly || options.readonly ||
                               (schema.readonly !== undefined && options.readonly !== undefined);
                schema.forceCommit = options.forceCommit || schema.forceCommit;

                viewClass.prototype.constructor.apply(this, arguments);
                if (this.model) {
                    this.listenTo(this.model, `change:${this.key}`, this.updateValue);
                    this.listenTo(this.model, 'sync', this.updateValue);
                }

                this.classes = classes;
            },

            __updateEmpty () {
                this.$el.toggleClass(classes.EMPTY, this.isEmptyValue());
            },

            /**
             * Manually updated editor's internal value with the value from this.model.get(this.key).
             * Shouldn't be called normally. The method is called internally on model's change event.
             * */
            updateValue: function () {
                this.setValue(this.getModelValue());
            },

            /**
             * Retrieves actual value of the bound attribute from the model.
             * @return {*}
             * */
            getModelValue: function () {
                return !this.model ? undefined : this.model.get(this.key);
            },

            __getFocusElement: function () {
                if (this.focusElement) {
                    return this.$el.find(this.focusElement);
                }
                return this.$el;
            },

            __triggerChange: function (...args) {
                this.trigger('change', this, ...args);
            },

            /**
             * Returns internal editor's value.
             * @return {*}
             */
            getValue: function() {
                return this.value;
            },

            /**
             * Sets new internal editor's value.
             * @param {*} value The new value.
             */
            setValue: function(value) {

                this.value = value;
            },

            setPermissions: function (enabled, readonly) {
                this.__setEnabled(enabled);
                this.__setReadonly(readonly);
            },

            /**
             * Sets a new value of enabled flag. While disabled, the editor's value cannot be changed or copied by the user.
             * It's implied that the value doesn't make sense.
             * @param {Boolean} enabled New flag value.
             */
            setEnabled: function (enabled) {
                let readonly = this.getReadonly();
                this.setPermissions(enabled, readonly);
            },

            /**
             * Sets a new value of readonly flag. While readonly, the editor's value cannot be changed but can be copied by the user.
             * @param {Boolean} readonly New flag value.
             */
            setReadonly: function (readonly) {
                let enabled = this.getEnabled();
                this.setPermissions(enabled, readonly);
            },

            __setEnabled: function (enabled) {
                this.enabled = enabled;
                this.trigger('enabled', enabled);
                if (!this.enabled) {
                    this.$el.addClass(classes.disabled);
                } else {
                    this.$el.removeClass(classes.disabled);
                }
            },

            /**
             * Returns the value of `enabled` flag.
             * @return {Boolean}
             */
            getEnabled: function () {
                return this.enabled;
            },

            __setReadonly: function (readonly) {
                this.readonly = readonly;
                this.trigger('readonly', readonly);
                if (this.readonly && this.getEnabled()) {
                    this.$el.addClass(classes.readonly);
                } else {
                    this.$el.removeClass(classes.readonly);
                }
            },

            /**
             * Returns the value of `readonly` flag.
             * @return {Boolean}
             */
            getReadonly: function () {
                return this.readonly;
            },

            /**
             * Sets the focus onto this editor.
             */
            focus: function() {
                this.__getFocusElement().focus();
                this.hasFocus = true;
            },

            /**
             * Clears the focus.
             */
            blur: function() {
                this.__getFocusElement().blur();
                this.hasFocus = false;
            },

            /**
             * Update the model with the internal editor's value.
             * @param {Object} [options] Options to pass to model.set().
             * @param {Boolean} [options.validate] Set to true to trigger built-in model validation.
             * @return {Object|undefined} Returns an error object { type, message } if validation fails
             * and options.forceCommit is turned off. undefined otherwise.
             */
            commit: function (options) {
                options = options || {};
                var error = this.validate();
                if (error && !this.schema.forceCommit) {
                    return error;
                }

                this.listenToOnce(this.model, 'invalid', function(model, e) {
                    error = e;
                });

                this.model.set(this.key, this.getValue(), {
                    silent: false,
                    validate: options.validate === true
                });

                if (error && !this.schema.forceCommit) {
                    return error;
                }
                this.trigger(`${this.key}:committed`, this, this.model, this.getValue());
                this.trigger('value:committed', this, this.model, this.key, this.getValue());
            },

            isEmptyValue: function () {
                return !this.getValue();
            },

            /**
             * Check validity with built-in validator functions (initially passed into constructor options).
             * @return {Object|undefined} Returns an error object { type, message } if validation fails. undefined otherwise.
             */
            validate: function() {
                let error = null;
                let value = this.getValue();
                let formValues = this.form ? this.form.getValue() : {};
                let validators = this.validators;
                let getValidator = this.getValidator;

                if (validators) {
                    //Run through validators until an error is found
                    _.every(validators, function(validator) {
                        error = getValidator(validator)(value, formValues);
                        return !error;
                    });
                }

                return error;
            },

            trigger: function(event) {
                if (event === 'focus') {
                    this.hasFocus = true;
                } else if (event === 'blur') {
                    this.hasFocus = false;
                }

                return Marionette.ItemView.prototype.trigger.apply(this, arguments);
            },

            getValidator: function(validator) {
                let validators = formRepository.validators;

                //Convert regular expressions to validators
                if (_.isRegExp(validator)) {
                    return validators.regexp({ regexp: validator });
                }

                //Use a built-in validator if given a string
                if (_.isString(validator)) {
                    if (!validators[validator]) {
                        throw new Error(`Validator "${validator}" not found`);
                    }

                    return validators[validator]();
                }

                //Functions can be used directly
                if (_.isFunction(validator)) {
                    return validator;
                }

                //Use a customised built-in validator if given an object
                //noinspection JSUnresolvedVariable
                if (_.isObject(validator) && validator.type) {
                    let config = validator;

                    //noinspection JSUnresolvedVariable
                    return validators[config.type](config);
                }

                //Unknown validator type
                throw new Error(`Invalid validator: ${validator}`);
            },

            onFocus: function () {
                this.$el.addClass(classes.FOCUSED);
                this.trigger('focus', this);
            },

            onBlur: function () {
                this.$el.removeClass(classes.FOCUSED);
                this.trigger('blur', this);
            }
        };
    }
};
<|endoftext|>


<commit_before>var traverseTree = require('../../shared/TraverseTree');

describe('TraverseTree', function () {
    it('calls the callback once with the object if it has no children', function () {
        var obj = {};
        var spy = jasmine.createSpy('spy');

        traverseTree(obj, spy);

        expect(spy.calls.count()).toEqual(1);
        expect(spy).toHaveBeenCalledWith(obj);
    });
});
<commit_msg>adding failing test to check travis<commit_after>var traverseTree = require('../../shared/TraverseTree');

describe('TraverseTree', function () {
    it('calls the callback once with the object if it has no children', function () {
        var obj = {};
        var spy = jasmine.createSpy('spy');

        traverseTree(obj, spy);

        expect(spy.calls.count()).toEqual(1);
        expect(spy).toHaveBeenCalledWith(obj);
    });

    it('should fail', function() {
        expect(true).toEqual(false);
    })
});
<|endoftext|>