<commit_before>/**
 * Copyright 2013-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule EventPluginHub
 */

'use strict';

var EventPluginRegistry = require('EventPluginRegistry');
var EventPluginUtils = require('EventPluginUtils');
var ReactErrorUtils = require('ReactErrorUtils');

var accumulateInto = require('accumulateInto');
var forEachAccumulated = require('forEachAccumulated');
var invariant = require('invariant');

/**
 * Internal store for event listeners
 */
var listenerBank = {};

/**
 * Internal queue of events that have accumulated their dispatches and are
 * waiting to have their dispatches executed.
 */
var eventQueue = null;

/**
 * Dispatches an event and releases it back into the pool, unless persistent.
 *
 * @param {?object} event Synthetic event to be dispatched.
 * @param {boolean} simulated If the event is simulated (changes exn behavior)
 * @private
 */
var executeDispatchesAndRelease = function(event, simulated) {
  if (event) {
    EventPluginUtils.executeDispatchesInOrder(event, simulated);

    if (!event.isPersistent()) {
      event.constructor.release(event);
    }
  }
};
var executeDispatchesAndReleaseSimulated = function(e) {
  return executeDispatchesAndRelease(e, true);
};
var executeDispatchesAndReleaseTopLevel = function(e) {
  return executeDispatchesAndRelease(e, false);
};

/**
 * This is a unified interface for event plugins to be installed and configured.
 *
 * Event plugins can implement the following properties:
 *
 *   `extractEvents` {function(string, DOMEventTarget, string, object): *}
 *     Required. When a top-level event is fired, this method is expected to
 *     extract synthetic events that will in turn be queued and dispatched.
 *
 *   `eventTypes` {object}
 *     Optional, plugins that fire events must publish a mapping of registration
 *     names that are used to register listeners. Values of this mapping must
 *     be objects that contain `registrationName` or `phasedRegistrationNames`.
 *
 *   `executeDispatch` {function(object, function, string)}
 *     Optional, allows plugins to override how an event gets dispatched. By
 *     default, the listener is simply invoked.
 *
 * Each plugin that is injected into `EventsPluginHub` is immediately operable.
 *
 * @public
 */
var EventPluginHub = {

  /**
   * Methods for injecting dependencies.
   */
  injection: {

    /**
     * @param {array} InjectedEventPluginOrder
     * @public
     */
    injectEventPluginOrder: EventPluginRegistry.injectEventPluginOrder,

    /**
     * @param {object} injectedNamesToPlugins Map from names to plugin modules.
     */
    injectEventPluginsByName: EventPluginRegistry.injectEventPluginsByName,

  },

  /**
   * Stores `listener` at `listenerBank[registrationName][id]`. Is idempotent.
   *
   * @param {object} inst The instance, which is the source of events.
   * @param {string} registrationName Name of listener (e.g. `onClick`).
   * @param {function} listener The callback to store.
   */
  putListener: function(inst, registrationName, listener) {
    invariant(
      typeof listener === 'function',
      'Expected %s listener to be a function, instead got type %s',
      registrationName, typeof listener
    );

    var bankForRegistrationName =
      listenerBank[registrationName] || (listenerBank[registrationName] = {});
    bankForRegistrationName[inst._rootNodeID] = listener;

    var PluginModule =
      EventPluginRegistry.registrationNameModules[registrationName];
    if (PluginModule && PluginModule.didPutListener) {
      PluginModule.didPutListener(inst, registrationName, listener);
    }
  },

  /**
   * @param {object} inst The instance, which is the source of events.
   * @param {string} registrationName Name of listener (e.g. `onClick`).
   * @return {?function} The stored callback.
   */
  getListener: function(inst, registrationName) {
    var bankForRegistrationName = listenerBank[registrationName];
    return bankForRegistrationName && bankForRegistrationName[inst._rootNodeID];
  },

  /**
   * Deletes a listener from the registration bank.
   *
   * @param {object} inst The instance, which is the source of events.
   * @param {string} registrationName Name of listener (e.g. `onClick`).
   */
  deleteListener: function(inst, registrationName) {
    var PluginModule =
      EventPluginRegistry.registrationNameModules[registrationName];
    if (PluginModule && PluginModule.willDeleteListener) {
      PluginModule.willDeleteListener(inst, registrationName);
    }

    var bankForRegistrationName = listenerBank[registrationName];
    // TODO: This should never be null -- when is it?
    if (bankForRegistrationName) {
      delete bankForRegistrationName[inst._rootNodeID];
    }
  },

  /**
   * Deletes all listeners for the DOM element with the supplied ID.
   *
   * @param {object} inst The instance, which is the source of events.
   */
  deleteAllListeners: function(inst) {
    for (var registrationName in listenerBank) {
      if (!listenerBank[registrationName][inst._rootNodeID]) {
        continue;
      }

      var PluginModule =
        EventPluginRegistry.registrationNameModules[registrationName];
      if (PluginModule && PluginModule.willDeleteListener) {
        PluginModule.willDeleteListener(inst, registrationName);
      }

      delete listenerBank[registrationName][inst._rootNodeID];
    }
  },

  /**
   * Allows registered plugins an opportunity to extract events from top-level
   * native browser events.
   *
   * @return {*} An accumulation of synthetic events.
   * @internal
   */
  extractEvents: function(
      topLevelType,
      targetInst,
      nativeEvent,
      nativeEventTarget) {
    var events;
    var plugins = EventPluginRegistry.plugins;
    for (var i = 0; i < plugins.length; i++) {
      // Not every plugin in the ordering may be loaded at runtime.
      var possiblePlugin = plugins[i];
      if (possiblePlugin) {
        var extractedEvents = possiblePlugin.extractEvents(
          topLevelType,
          targetInst,
          nativeEvent,
          nativeEventTarget
        );
        if (extractedEvents) {
          events = accumulateInto(events, extractedEvents);
        }
      }
    }
    return events;
  },

  /**
   * Enqueues a synthetic event that should be dispatched when
   * `processEventQueue` is invoked.
   *
   * @param {*} events An accumulation of synthetic events.
   * @internal
   */
  enqueueEvents: function(events) {
    if (events) {
      eventQueue = accumulateInto(eventQueue, events);
    }
  },

  /**
   * Dispatches all synthetic events on the event queue.
   *
   * @internal
   */
  processEventQueue: function(simulated) {
    // Set `eventQueue` to null before processing it so that we can tell if more
    // events get enqueued while processing.
    var processingEventQueue = eventQueue;
    eventQueue = null;
    if (simulated) {
      forEachAccumulated(
        processingEventQueue,
        executeDispatchesAndReleaseSimulated
      );
    } else {
      forEachAccumulated(
        processingEventQueue,
        executeDispatchesAndReleaseTopLevel
      );
    }
    invariant(
      !eventQueue,
      'processEventQueue(): Additional events were enqueued while processing ' +
      'an event queue. Support for this has not yet been implemented.'
    );
    // This would be a good time to rethrow if any of the event handlers threw.
    ReactErrorUtils.rethrowCaughtError();
  },

  /**
   * These are needed for tests only. Do not use!
   */
  __purge: function() {
    listenerBank = {};
  },

  __getListenerBank: function() {
    return listenerBank;
  },

};

module.exports = EventPluginHub;
<commit_msg>Added own property check when deleting listeners.<commit_after>/**
 * Copyright 2013-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule EventPluginHub
 */

'use strict';

var EventPluginRegistry = require('EventPluginRegistry');
var EventPluginUtils = require('EventPluginUtils');
var ReactErrorUtils = require('ReactErrorUtils');

var accumulateInto = require('accumulateInto');
var forEachAccumulated = require('forEachAccumulated');
var invariant = require('invariant');

/**
 * Internal store for event listeners
 */
var listenerBank = {};

/**
 * Internal queue of events that have accumulated their dispatches and are
 * waiting to have their dispatches executed.
 */
var eventQueue = null;

/**
 * Dispatches an event and releases it back into the pool, unless persistent.
 *
 * @param {?object} event Synthetic event to be dispatched.
 * @param {boolean} simulated If the event is simulated (changes exn behavior)
 * @private
 */
var executeDispatchesAndRelease = function(event, simulated) {
  if (event) {
    EventPluginUtils.executeDispatchesInOrder(event, simulated);

    if (!event.isPersistent()) {
      event.constructor.release(event);
    }
  }
};
var executeDispatchesAndReleaseSimulated = function(e) {
  return executeDispatchesAndRelease(e, true);
};
var executeDispatchesAndReleaseTopLevel = function(e) {
  return executeDispatchesAndRelease(e, false);
};

/**
 * This is a unified interface for event plugins to be installed and configured.
 *
 * Event plugins can implement the following properties:
 *
 *   `extractEvents` {function(string, DOMEventTarget, string, object): *}
 *     Required. When a top-level event is fired, this method is expected to
 *     extract synthetic events that will in turn be queued and dispatched.
 *
 *   `eventTypes` {object}
 *     Optional, plugins that fire events must publish a mapping of registration
 *     names that are used to register listeners. Values of this mapping must
 *     be objects that contain `registrationName` or `phasedRegistrationNames`.
 *
 *   `executeDispatch` {function(object, function, string)}
 *     Optional, allows plugins to override how an event gets dispatched. By
 *     default, the listener is simply invoked.
 *
 * Each plugin that is injected into `EventsPluginHub` is immediately operable.
 *
 * @public
 */
var EventPluginHub = {

  /**
   * Methods for injecting dependencies.
   */
  injection: {

    /**
     * @param {array} InjectedEventPluginOrder
     * @public
     */
    injectEventPluginOrder: EventPluginRegistry.injectEventPluginOrder,

    /**
     * @param {object} injectedNamesToPlugins Map from names to plugin modules.
     */
    injectEventPluginsByName: EventPluginRegistry.injectEventPluginsByName,

  },

  /**
   * Stores `listener` at `listenerBank[registrationName][id]`. Is idempotent.
   *
   * @param {object} inst The instance, which is the source of events.
   * @param {string} registrationName Name of listener (e.g. `onClick`).
   * @param {function} listener The callback to store.
   */
  putListener: function(inst, registrationName, listener) {
    invariant(
      typeof listener === 'function',
      'Expected %s listener to be a function, instead got type %s',
      registrationName, typeof listener
    );

    var bankForRegistrationName =
      listenerBank[registrationName] || (listenerBank[registrationName] = {});
    bankForRegistrationName[inst._rootNodeID] = listener;

    var PluginModule =
      EventPluginRegistry.registrationNameModules[registrationName];
    if (PluginModule && PluginModule.didPutListener) {
      PluginModule.didPutListener(inst, registrationName, listener);
    }
  },

  /**
   * @param {object} inst The instance, which is the source of events.
   * @param {string} registrationName Name of listener (e.g. `onClick`).
   * @return {?function} The stored callback.
   */
  getListener: function(inst, registrationName) {
    var bankForRegistrationName = listenerBank[registrationName];
    return bankForRegistrationName && bankForRegistrationName[inst._rootNodeID];
  },

  /**
   * Deletes a listener from the registration bank.
   *
   * @param {object} inst The instance, which is the source of events.
   * @param {string} registrationName Name of listener (e.g. `onClick`).
   */
  deleteListener: function(inst, registrationName) {
    var PluginModule =
      EventPluginRegistry.registrationNameModules[registrationName];
    if (PluginModule && PluginModule.willDeleteListener) {
      PluginModule.willDeleteListener(inst, registrationName);
    }

    var bankForRegistrationName = listenerBank[registrationName];
    // TODO: This should never be null -- when is it?
    if (bankForRegistrationName) {
      delete bankForRegistrationName[inst._rootNodeID];
    }
  },

  /**
   * Deletes all listeners for the DOM element with the supplied ID.
   *
   * @param {object} inst The instance, which is the source of events.
   */
  deleteAllListeners: function(inst) {
    for (var registrationName in listenerBank) {
      if (!listenerBank.hasOwnProperty(registrationName)) {
        continue;
      }

      if (!listenerBank[registrationName][inst._rootNodeID]) {
        continue;
      }

      var PluginModule =
        EventPluginRegistry.registrationNameModules[registrationName];
      if (PluginModule && PluginModule.willDeleteListener) {
        PluginModule.willDeleteListener(inst, registrationName);
      }

      delete listenerBank[registrationName][inst._rootNodeID];
    }
  },

  /**
   * Allows registered plugins an opportunity to extract events from top-level
   * native browser events.
   *
   * @return {*} An accumulation of synthetic events.
   * @internal
   */
  extractEvents: function(
      topLevelType,
      targetInst,
      nativeEvent,
      nativeEventTarget) {
    var events;
    var plugins = EventPluginRegistry.plugins;
    for (var i = 0; i < plugins.length; i++) {
      // Not every plugin in the ordering may be loaded at runtime.
      var possiblePlugin = plugins[i];
      if (possiblePlugin) {
        var extractedEvents = possiblePlugin.extractEvents(
          topLevelType,
          targetInst,
          nativeEvent,
          nativeEventTarget
        );
        if (extractedEvents) {
          events = accumulateInto(events, extractedEvents);
        }
      }
    }
    return events;
  },

  /**
   * Enqueues a synthetic event that should be dispatched when
   * `processEventQueue` is invoked.
   *
   * @param {*} events An accumulation of synthetic events.
   * @internal
   */
  enqueueEvents: function(events) {
    if (events) {
      eventQueue = accumulateInto(eventQueue, events);
    }
  },

  /**
   * Dispatches all synthetic events on the event queue.
   *
   * @internal
   */
  processEventQueue: function(simulated) {
    // Set `eventQueue` to null before processing it so that we can tell if more
    // events get enqueued while processing.
    var processingEventQueue = eventQueue;
    eventQueue = null;
    if (simulated) {
      forEachAccumulated(
        processingEventQueue,
        executeDispatchesAndReleaseSimulated
      );
    } else {
      forEachAccumulated(
        processingEventQueue,
        executeDispatchesAndReleaseTopLevel
      );
    }
    invariant(
      !eventQueue,
      'processEventQueue(): Additional events were enqueued while processing ' +
      'an event queue. Support for this has not yet been implemented.'
    );
    // This would be a good time to rethrow if any of the event handlers threw.
    ReactErrorUtils.rethrowCaughtError();
  },

  /**
   * These are needed for tests only. Do not use!
   */
  __purge: function() {
    listenerBank = {};
  },

  __getListenerBank: function() {
    return listenerBank;
  },

};

module.exports = EventPluginHub;
<|endoftext|>


<commit_before>"use strict";

const mongoose = require('mongoose');

module.exports = function (application, done) {

    application.db = mongoose.connection;

    application.models = require('./common/models')(application);

    application.db.on('error', function (err) {
        application.log.error(err);
    });
    application.db.on('connected', function() {
        application.log.info('MongoDB connected!');
    });
    application.db.once('open', function() {
        application.log.info('MongoDB connection opened!');
    });
    application.db.on('reconnected', function () {
        application.log.info('MongoDB reconnected!');
    });
    application.db.on('disconnected', function() {
        application.log.info('MongoDB disconnected!');

    });

    mongoose.connect(application.config.mongo.uri, application.config.mongo.options, done);
};

// Close the Mongoose connection, when receiving SIGINT
process.on('SIGINT', function() {
    mongoose.connection.close(function () {
        console.log('Force to close the MongoDB conection');
        process.exit(0);
    });
});
<commit_msg>log error if connection refused on startup - mongo will connect later if mongodb is offline - and reconnect later<commit_after>"use strict";

const mongoose = require('mongoose');

module.exports = function (application, done) {

    application.db = mongoose.connection;

    application.models = require('./common/models')(application);

    application.db.on('error', function (err) {
        application.log.error(err);
    });
    application.db.on('connected', function() {
        application.log.info('MongoDB connected!');
    });
    application.db.once('open', function() {
        application.log.info('MongoDB connection opened!');
    });
    application.db.on('reconnected', function () {
        application.log.info('MongoDB reconnected!');
    });
    application.db.on('disconnected', function() {
        application.log.info('MongoDB disconnected!');

    });

    mongoose.connect(application.config.mongo.uri, application.config.mongo.options, function(err) {
        if(err) {
            application.log.error(err);
        }

        done();
    });
};

// Close the Mongoose connection, when receiving SIGINT
process.on('SIGINT', function() {
    mongoose.connection.close(function () {
        console.log('Force to close the MongoDB conection');
        process.exit(0);
    });
});
<|endoftext|>


<commit_before>import React from 'react'
import classes from './QueueItem.css'

export const QueueItem = (props) => (

      {props.isActive ? 'now' : props.wait}

{props.name}

{props.artist} - {props.title}

    {props.hasErrors &&

        info_outline

    }

        {props.isStarred ? 'star' : 'star_border'}

    {props.canSkip &&

        skip_next

    }
    {props.canRemove &&

        clear

    }

)

QueueItem.propTypes = {
  title: React.PropTypes.string.isRequired,
  artist: React.PropTypes.string.isRequired,
  name: React.PropTypes.string.isRequired,
  hasErrors: React.PropTypes.bool.isRequired,
  canSkip: React.PropTypes.bool.isRequired,
  onSkipClick: React.PropTypes.func.isRequired,
  canRemove: React.PropTypes.bool.isRequired,
  onRemoveClick: React.PropTypes.func.isRequired,
  onErrorInfoClick: React.PropTypes.func.isRequired,
}

export default QueueItem
<commit_msg>Remove star icon from QueueItems for now<commit_after>import React from 'react'
import classes from './QueueItem.css'

export const QueueItem = (props) => (

      {props.isActive ? 'now' : props.wait}

{props.name}

{props.artist} - {props.title}

    {props.hasErrors &&

        info_outline

    }
    {props.canSkip &&

        skip_next

    }
    {props.canRemove &&

        clear

    }

)

QueueItem.propTypes = {
  title: React.PropTypes.string.isRequired,
  artist: React.PropTypes.string.isRequired,
  name: React.PropTypes.string.isRequired,
  hasErrors: React.PropTypes.bool.isRequired,
  canSkip: React.PropTypes.bool.isRequired,
  onSkipClick: React.PropTypes.func.isRequired,
  canRemove: React.PropTypes.bool.isRequired,
  onRemoveClick: React.PropTypes.func.isRequired,
  onErrorInfoClick: React.PropTypes.func.isRequired,
}

export default QueueItem
<|endoftext|>


<commit_before>'use strict';
/*
 Oressource
 Copyright (C) 2014-2017  Martin Vert and Oressource devellopers

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU Affero General Public License as
 published by the Free Software Foundation, either version 3 of the
 License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Affero General Public License for more details.

 You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// use Ticket from ticket.js

Ticket.prototype.sum_quantite = function () {
  return this.to_array()
      .reduce((acc, vente) => acc + vente.quantite, 0);
}

Ticket.prototype.sum_prix = function () {
  return this.to_array().reduce(
    (acc, vente) =>
      acc + (vente.lot ? vente.prix : (vente.prix * vente.quantite)),
    0.0);
}

function new_state() {
  const s = {
    moyen: 1, // especes
    ticket: new Ticket(),
    last: undefined,
    vente_unite: true
  };
  // Hack pour les impressions...
  window.OressourceEnv.tickets = s.ticket;
  return s;
}

function new_numpad() {
  return {
    prix: 0,
    quantite: 0,
    masse: 0.0
  };
}

function new_rendu() {
  return {
    reglement: 0,
    difference: 0
  };
}

const wrapString = (s, n, c) => (s.length > n) ? (s.slice(0, n) + c) : s;

let state = new_state();
let numpad = new_numpad();
let rendu = new_rendu();

let current_focus = document.getElementById('quantite');
function fokus(element) {
  current_focus = element;
}

function render_numpad({ prix, quantite, masse }) {
  document.getElementById('quantite').value = quantite;
  document.getElementById('prix').value = prix;
  if (window.OressourceEnv) {
    document.getElementById('masse').value = masse;
  }
}

function reset_rendu() {
  rendu = new_rendu();
  update_rendu();
}

function reset_numpad() {
  numpad = new_numpad();
  render_numpad(numpad);
}

function get_numpad() {
  const masseinput = document.getElementById('masse');
  return {
    quantite: Number.parseInt(document.getElementById('quantite').value, 10),
    masse: Number.parseFloat(masseinput === null ? NaN : masseinput.value, 10),
    prix: Number.parseFloat(document.getElementById('prix').value, 10)
  };
}

function update_state({ type, objet = { prix: 0, masse: 0.0 } }) {
  numpad.prix = objet.prix;
  numpad.quantite = 1;
  numpad.masse = objet.masse || 0.0;
  state.last = { type, objet };
  const color = objet.couleur || type.couleur;
  const name = objet.nom || type.nom;
  const html = `<span class='badge' id='cool' style="background-color:${color}">${wrapString(name, 15, '…')}</span>`;
  document.getElementById('nom_objet').innerHTML = html;
  render_numpad(numpad);
}

function reset(data, response) {
  state = new_state();
  reset_numpad();
  reset_rendu();
  const range = document.createRange();
  range.selectNodeContents(document.getElementById('transaction'));
  range.deleteContents();
  update_recap(0.0, 0.0);
  document.getElementById('num_vente').textContent = response.id + 1;
}

function moyens(moyen) {
  state.moyen = moyen;
}

function encaisse_vente() {
  if (state.ticket.size > 0) {
    const url = '../api/ventes.php';
    const date = document.getElementById('date');
    const data = {
      classe: 'ventes',
      id_point: window.OressourceEnv.point.id,
      id_user: window.OressourceEnv.id_user,
      id_moyen: state.moyen,
      commentaire: document.getElementById('commentaire').value.trim(),
      items: state.ticket.to_array()
    };
    if (date !== null) {
      data.date = date.value;
    }
    return data;
  } else {
    return {};
  }
}

// Immediatly calling function to get how to print TVA
const printTva = (() => {
  if (window.OressourceEnv.tva_active) {
      return () => {
      const prixtot = state.ticket.sum_prix().toFixed(2);
      const taux_tva = window.OressourceEnv.taux_tva.toFixed(2);
      const ptva = prixtot * taux_tva;
      const prixht = (prixtot - ptva).toFixed(2);
      return `TVA à ${taux_tva}% Prix H.T. = ${prixht} + € TVA =  ${ptva} €`;
    }
  } else {
    () => "Association non assujettie à la TVA.";
  }
})();

function update_rendu() {
  const total = state.ticket.sum_prix();
  const input = document.getElementById('reglement');
  rendu.reglement = parseFloat(input.value, 10).toFixed(3);
  rendu.difference = rendu.reglement - total;
  document.getElementById('somme').value = total;
  document.getElementById('difference').value = rendu.difference || 0;
}

function numpad_input(elem) {
  current_focus.value += elem.value;
}

function remove(id) {
  const elem = state.ticket.remove(id);
  document.getElementById(id).remove();
  update_rendu();
  update_recap(state.ticket.sum_prix(), state.ticket.sum_quantite());
  reset_numpad();
}

function update_recap(total, size) {
  document.getElementById('total').innerHTML = `<li class="list-group-item">Soit : ${size} article(s) pour : <span class="badge" style="float:right;">${total.toFixed(2)} €</span></li>`;
  document.getElementById('recaptotal').innerHTML = total.toFixed(2) + ' €';
  document.getElementById('nom_objet').textContent = "Objet:";
}

/**
 * Cette fonction est utilisée lorsque l'on click sur le bouton `ajouter`
 * du numpad.
 * On récupére l'état des objets du même type à ajouter au panier puis
 * on met a jour l'interface graphique du panier.
 *
 * Deux points sont assez particulier:
 *
 * ## Cas de la vente en lot
 *
 * En lot on décide d'appliquer un prix arbitraire à un ensemble d'objet
 * du même type sans respecter le prix unitaire (une promotion en somme).
 * On ne peux donc pas utilier comme "prix total" quantité * prix
 * comme dans la vente unitaire. On prends le prix seulement du lot.
 *
 * ## Cas des pesées en vente:
 *
 * On ajoute la masse de l'ensemble des objets à ajouter au panier
 * (Comme dans une collecte ou sortie), on ne pesee pas independament tout
 * les objets on pesee en lot en somme.
 */
function add() {
  if (state.last !== undefined) {
    const { prix, quantite, masse } = get_numpad();
    if (quantite > 0 && !isNaN(prix)) {
      const current = state.last;
      state.last = undefined;
      // Idée: Ajouter un champ "prix total" pour eviter de faire un if pour les calculs sur les prix.

      const name = current.objet.nom || current.type.nom;
      const vente = {
        id_type: current.type.id,
        id_objet: current.objet.id || null,
        lot: !state.vente_unite,
        quantite,
        prix,
        masse,
        name, // Hack pour les impressions.
      };

      vente.show = function() {
        const prix_txt = `${this.prix} €`;
        const masse_txt = this.masse >= 0.00 ? ` ${this.masse} kg` : '';
        return `<p>${this.name} : ${prix_txt}${masse_txt}</p>`;
      };

      const id = state.ticket.push(vente);

      const li = document.createElement('li');
      li.setAttribute('id', id);
      li.setAttribute('class', 'list-group-item');
      const amount = vente.lot ?  vente.prix : vente.prix * vente.quantite;
      let html = `
            ${amount.toFixed(2)} €

               ${quantite} × ${name}`;
      if (masse > 0 && window.OressourceEnv.pesees) {
        html += `, ${(masse).toFixed(3)} Kgs.`;
      }
      li.innerHTML = html;
      document.getElementById('transaction').appendChild(li);
      update_recap(state.ticket.sum_prix(), state.ticket.sum_quantite());
      update_rendu();
      reset_numpad();
    } else {
      this.input.setCustomValidity('Quantite nulle ou inferieur a 0.');
    }
  }
}

document.addEventListener('DOMContentLoaded', () => {
  $("#typeVente").bootstrapSwitch();
  $("#typeVente").on('switchChange.bootstrapSwitch', (event, checked) => {
    const lot_or_unite = (label, prix_string, masse_string, bg_color) => {
      document.getElementById('labellot').textContent = label;
      document.getElementById('labelprix').textContent = prix_string;
      document.getElementById('panelcalc').style.backgroundColor = bg_color;
      if (window.OressourceEnv.pesees) {
        document.getElementById('labelmasse').textContent = masse_string;
      }
    };
    if (checked) {
      lot_or_unite("Vente à: ", "Prix unitaire:", "Masse unitaire: ", "white");
    } else {
      lot_or_unite("Vente au: ", "Prix du lot: ", "Masse du lot: ", "#E8E6BC");
    }
    state.vente_unite = checked;
  });

  const url = '../api/ventes.php';
  const ventePrint = (data, response) => {
    return impression_ticket(data, response, '€', printTva, (t) => t.sum_prix());
  }
  const send = post_data(url, encaisse_vente, reset);
  const sendAndPrint = post_data(url, encaisse_vente, reset, ventePrint);
  document.getElementById('encaissement').addEventListener('click', send, false);
  document.getElementById('impression').addEventListener('click', sendAndPrint, false);

}, false);
<commit_msg>[ventes] Fix bug liée à une typo.<commit_after>'use strict';
/*
 Oressource
 Copyright (C) 2014-2017  Martin Vert and Oressource devellopers

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU Affero General Public License as
 published by the Free Software Foundation, either version 3 of the
 License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Affero General Public License for more details.

 You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// use Ticket from ticket.js

Ticket.prototype.sum_quantite = function () {
  return this.to_array()
      .reduce((acc, vente) => acc + vente.quantite, 0);
}

Ticket.prototype.sum_prix = function () {
  return this.to_array().reduce(
    (acc, vente) =>
      acc + (vente.lot ? vente.prix : (vente.prix * vente.quantite)),
    0.0);
}

function new_state() {
  const s = {
    moyen: 1, // especes
    ticket: new Ticket(),
    last: undefined,
    vente_unite: true
  };
  // Hack pour les impressions...
  window.OressourceEnv.tickets = s.ticket;
  return s;
}

function new_numpad() {
  return {
    prix: 0,
    quantite: 0,
    masse: 0.0
  };
}

function new_rendu() {
  return {
    reglement: 0,
    difference: 0
  };
}

const wrapString = (s, n, c) => (s.length > n) ? (s.slice(0, n) + c) : s;

let state = new_state();
let numpad = new_numpad();
let rendu = new_rendu();

let current_focus = document.getElementById('quantite');
function fokus(element) {
  current_focus = element;
}

function render_numpad({ prix, quantite, masse }) {
  document.getElementById('quantite').value = quantite;
  document.getElementById('prix').value = prix;
  if (window.OressourceEnv.pesees) {
    document.getElementById('masse').value = masse;
  }
}

function reset_rendu() {
  rendu = new_rendu();
  update_rendu();
}

function reset_numpad() {
  numpad = new_numpad();
  render_numpad(numpad);
}

function get_numpad() {
  const masseinput = document.getElementById('masse');
  return {
    quantite: Number.parseInt(document.getElementById('quantite').value, 10),
    masse: Number.parseFloat(masseinput === null ? NaN : masseinput.value, 10),
    prix: Number.parseFloat(document.getElementById('prix').value, 10)
  };
}

function update_state({ type, objet = { prix: 0, masse: 0.0 } }) {
  numpad.prix = objet.prix;
  numpad.quantite = 1;
  numpad.masse = objet.masse || 0.0;
  state.last = { type, objet };
  const color = objet.couleur || type.couleur;
  const name = objet.nom || type.nom;
  const html = `<span class='badge' id='cool' style="background-color:${color}">${wrapString(name, 15, '…')}</span>`;
  document.getElementById('nom_objet').innerHTML = html;
  render_numpad(numpad);
}

function reset(data, response) {
  state = new_state();
  reset_numpad();
  reset_rendu();
  const range = document.createRange();
  range.selectNodeContents(document.getElementById('transaction'));
  range.deleteContents();
  update_recap(0.0, 0.0);
  document.getElementById('num_vente').textContent = response.id + 1;
}

function moyens(moyen) {
  state.moyen = moyen;
}

function encaisse_vente() {
  if (state.ticket.size > 0) {
    const url = '../api/ventes.php';
    const date = document.getElementById('date');
    const data = {
      classe: 'ventes',
      id_point: window.OressourceEnv.point.id,
      id_user: window.OressourceEnv.id_user,
      id_moyen: state.moyen,
      commentaire: document.getElementById('commentaire').value.trim(),
      items: state.ticket.to_array()
    };
    if (date !== null) {
      data.date = date.value;
    }
    return data;
  } else {
    return {};
  }
}

// Immediatly calling function to get how to print TVA
const printTva = (() => {
  if (window.OressourceEnv.tva_active) {
      return () => {
      const prixtot = state.ticket.sum_prix().toFixed(2);
      const taux_tva = window.OressourceEnv.taux_tva.toFixed(2);
      const ptva = prixtot * taux_tva;
      const prixht = (prixtot - ptva).toFixed(2);
      return `TVA à ${taux_tva}% Prix H.T. = ${prixht} + € TVA =  ${ptva} €`;
    }
  } else {
    () => "Association non assujettie à la TVA.";
  }
})();

function update_rendu() {
  const total = state.ticket.sum_prix();
  const input = document.getElementById('reglement');
  rendu.reglement = parseFloat(input.value, 10).toFixed(3);
  rendu.difference = rendu.reglement - total;
  document.getElementById('somme').value = total;
  document.getElementById('difference').value = rendu.difference || 0;
}

function numpad_input(elem) {
  current_focus.value += elem.value;
}

function remove(id) {
  const elem = state.ticket.remove(id);
  document.getElementById(id).remove();
  update_rendu();
  update_recap(state.ticket.sum_prix(), state.ticket.sum_quantite());
  reset_numpad();
}

function update_recap(total, size) {
  document.getElementById('total').innerHTML = `<li class="list-group-item">Soit : ${size} article(s) pour : <span class="badge" style="float:right;">${total.toFixed(2)} €</span></li>`;
  document.getElementById('recaptotal').innerHTML = total.toFixed(2) + ' €';
  document.getElementById('nom_objet').textContent = "Objet:";
}

/**
 * Cette fonction est utilisée lorsque l'on click sur le bouton `ajouter`
 * du numpad.
 * On récupére l'état des objets du même type à ajouter au panier puis
 * on met a jour l'interface graphique du panier.
 *
 * Deux points sont assez particulier:
 *
 * ## Cas de la vente en lot
 *
 * En lot on décide d'appliquer un prix arbitraire à un ensemble d'objet
 * du même type sans respecter le prix unitaire (une promotion en somme).
 * On ne peux donc pas utilier comme "prix total" quantité * prix
 * comme dans la vente unitaire. On prends le prix seulement du lot.
 *
 * ## Cas des pesées en vente:
 *
 * On ajoute la masse de l'ensemble des objets à ajouter au panier
 * (Comme dans une collecte ou sortie), on ne pesee pas independament tout
 * les objets on pesee en lot en somme.
 */
function add() {
  if (state.last !== undefined) {
    const { prix, quantite, masse } = get_numpad();
    if (quantite > 0 && !isNaN(prix)) {
      const current = state.last;
      state.last = undefined;
      // Idée: Ajouter un champ "prix total" pour eviter de faire un if pour les calculs sur les prix.

      const name = current.objet.nom || current.type.nom;
      const vente = {
        id_type: current.type.id,
        id_objet: current.objet.id || null,
        lot: !state.vente_unite,
        quantite,
        prix,
        masse,
        name, // Hack pour les impressions.
      };

      vente.show = function() {
        const prix_txt = `${this.prix} €`;
        const masse_txt = this.masse >= 0.00 ? ` ${this.masse} kg` : '';
        return `<p>${this.name} : ${prix_txt}${masse_txt}</p>`;
      };

      const id = state.ticket.push(vente);

      const li = document.createElement('li');
      li.setAttribute('id', id);
      li.setAttribute('class', 'list-group-item');
      const amount = vente.lot ?  vente.prix : vente.prix * vente.quantite;
      let html = `
            ${amount.toFixed(2)} €

               ${quantite} × ${name}`;
      if (masse > 0 && window.OressourceEnv.pesees) {
        html += `, ${(masse).toFixed(3)} Kgs.`;
      }
      li.innerHTML = html;
      document.getElementById('transaction').appendChild(li);
      update_recap(state.ticket.sum_prix(), state.ticket.sum_quantite());
      update_rendu();
      reset_numpad();
    } else {
      this.input.setCustomValidity('Quantite nulle ou inferieur a 0.');
    }
  }
}

document.addEventListener('DOMContentLoaded', () => {
  $("#typeVente").bootstrapSwitch();
  $("#typeVente").on('switchChange.bootstrapSwitch', (event, checked) => {
    const lot_or_unite = (label, prix_string, masse_string, bg_color) => {
      document.getElementById('labellot').textContent = label;
      document.getElementById('labelprix').textContent = prix_string;
      document.getElementById('panelcalc').style.backgroundColor = bg_color;
      if (window.OressourceEnv.pesees) {
        document.getElementById('labelmasse').textContent = masse_string;
      }
    };
    if (checked) {
      lot_or_unite("Vente à: ", "Prix unitaire:", "Masse unitaire: ", "white");
    } else {
      lot_or_unite("Vente au: ", "Prix du lot: ", "Masse du lot: ", "#E8E6BC");
    }
    state.vente_unite = checked;
  });

  const url = '../api/ventes.php';
  const ventePrint = (data, response) => {
    return impression_ticket(data, response, '€', printTva, (t) => t.sum_prix());
  }
  const send = post_data(url, encaisse_vente, reset);
  const sendAndPrint = post_data(url, encaisse_vente, reset, ventePrint);
  document.getElementById('encaissement').addEventListener('click', send, false);
  document.getElementById('impression').addEventListener('click', sendAndPrint, false);

}, false);
<|endoftext|>


<commit_before>/*
TODO
(function(factory){
    if (typeof define === "function" && define.amd) {
        define(['jquery','leaflet'], factory);
    } else if (typeof exports === 'object') {
        factory(require('jquery'), require('leaflet'));
    } else {
        factory(jQuery, L);
    }
}*/
(function(jQuery, L){

	var $ = jQuery;

	$.fn.leafletLocationPicker = function(opts, onChangeLocation) {

		var baseClassName = 'leaflet-locpicker',
			baseLayers = {
				'OSM': 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
				'SAT': 'http://otile1.mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.png'
				//TODO add more free base layers
			};

		var optsMap = {
				zoom: 14,
				center: L.latLng([ 50.08, 14.43 ]),
				zoomControl: false,
				attributionControl: false
			};

		if($.isPlainObject(opts) && $.isPlainObject(opts.map))
			optsMap = $.extend(optsMap, opts.map);

		var defaults = {
			className: baseClassName,
			location: optsMap.center,
			locationFormat: '{lat}{sep}{lng}',
			locationMarker: true,
			locationDigits: 6,
			locationSep: ',',
			activeOnMove: false,
			position: 'topright',
			layer: 'OSM',
			height: 120,
			width: 180,
			map: optsMap,
			onChangeLocation: $.noop
		};

		if($.isPlainObject(opts))
			opts = $.extend(defaults, opts);

		else if($.isFunction(opts))
			opts = $.extend(defaults, {
				onChangeLocation: opts
			});
		else
			opts = defaults;

		if($.isFunction(onChangeLocation))
			opts = $.extend(defaults, {
				onChangeLocation: onChangeLocation
			});

		function roundLocation(loc) {
			return loc ? L.latLng(
				parseFloat(loc.lat).toFixed(opts.locationDigits),
				parseFloat(loc.lng).toFixed(opts.locationDigits)
			) : loc;
		}

		function parseLocation(loc) {
			var retLoc = loc;

			switch($.type(loc)) {
				case 'string':
					var ll = loc.split(opts.locationSep);
					if(ll[0] && ll[1])
						retLoc = L.latLng(ll);
					else
						retLoc = null;
				break;
				/*
				case 'array':
					retLoc = L.latLng(loc);
				break;
				case 'object':
					var lat, lng;

					if(loc.hasOwnProperty('lat'))
						lat = loc.lat;
					else if(loc.hasOwnProperty('latitude'))
						lat = loc.latitude;

					if(loc.hasOwnProperty('lng'))
						lng = loc.lng;
					else if(loc.hasOwnProperty('lon'))
						lng = loc.lon;
					else if(loc.hasOwnProperty('longitude'))
						lng = loc.longitude;

					retLoc = L.latLng(parseFloat(lat),parseFloat(lng));
				break;
				*/
				default:
					retLoc = loc;
			}
			return roundLocation( retLoc );
		}

		function buildMap(self) {

			self.divMap = document.createElement('div');
			self.$map = $(document.createElement('div'))
				.addClass(opts.className + '-map')
				.height(opts.height)
				.width(opts.width)
				.append(self.divMap)
				.appendTo('body');

			if(self.location)
				opts.map.center = self.location;

			if(typeof opts.layer === 'string' && baseLayers[opts.layer])
				opts.map.layers = L.tileLayer(baseLayers[opts.layer]);

			else if(opts.layer instanceof L.TileLayer ||
					opts.layer instanceof L.LayerGroup )
				opts.map.layers = opts.layer;

			else
				opts.map.layers = L.tileLayer(baseLayers.OSM);

			//leaflet map
			self.map = L.map(self.divMap, opts.map)
				.addControl( L.control.zoom({position:'bottomright'}) )
				.on('click', function(e) {
					self.setLocation(e.latlng);
				});

			if(opts.activeOnMove) {
				self.map.on('move', function(e) {
					self.setLocation(e.target.getCenter());
				});
			}

			var xmap = L.control({position: 'topright'});
			xmap.onAdd = function(map) {
				var btn = L.DomUtil.create('div','leaflet-control '+opts.className+'-close');
				btn.innerHTML = '×';
				L.DomEvent
					.on(btn, 'click', L.DomEvent.stop, self)
					.on(btn, 'click', self.closeMap, self);
				return btn;
			};
			xmap.addTo(self.map);

			if(opts.locationMarker)
				self.marker = buildMarker(self.location).addTo(self.map);

			return self.$map;
		}

		function buildMarker(loc) {
			var css = 'padding:0; margin:-1px; position: absolute; outline: 1px solid #fff; box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.6);';

			return L.marker( parseLocation(loc) || L.latLng(0,0), {
				icon: L.divIcon({
					className: opts.className+'-marker',
					iconAnchor: L.point(0, 0),
					html: '
'+
							'

'+
							'

'+
							'

'+
							'

'+
						'
',
				})
			});
		}

		$(this).each(function(index, input) {
		    var self = this;

		    self.$input = $(this);

		    self.locationOri = self.$input.val();

			self.onChangeLocation = function() {
				var edata = {
					latlng: self.location,
					location: self.getLocation()
				};
				self.$input.trigger($.extend(edata, {
					type: 'changeLocation'
				}));
				opts.onChangeLocation.call(self, edata);
			};

		    self.setLocation = function(loc, nocall) {
		    	self.location = parseLocation(loc);
		    	if(self.marker)
		    		self.marker.setLatLng(loc);
		    	self.$input.data('location', self.location);
		    	self.$input.val( self.getLocation() );
		    	self.onChangeLocation();
		    };

		    self.getLocation = function() {
		    	return self.location ? L.Util.template(opts.locationFormat, {
		    		lat: self.location.lat,
		    		lng: self.location.lng,
		    		sep: opts.locationSep
		    	}) : self.location;
		    };

		    self.openMap = function() {
			    switch(opts.position) {
				    case 'bottomleft':
					    self.$map.css({
						    top: self.$input.offset().top + self.$input.height() + 6,
						    left: self.$input.offset().left
					    });
					    break;
				    case 'topright':
					    self.$map.css({
						    top: self.$input.offset().top,
						    left: self.$input.offset().left + self.$input.width() + 5
					    });
					    break;
			    }

			    self.$map.show();
			    self.map.invalidateSize();
			    self.$input.trigger('show');
		    };

		    self.closeMap = function() {
			    self.$map.hide();
			    self.$input.trigger('hide');
		    };

		    self.setLocation(self.locationOri);

		    self.$map = buildMap(self);

		    self.$input
			    .addClass(opts.className)
			    .on('focus.'+opts.className, function(e) {
				    console.log('focus:', e);
				    e.preventDefault();
				    self.openMap();
			    })
			    .on('blur.'+opts.className, function(e) {
				    e.preventDefault();
				    var p = e.relatedTarget;
				    var close = true;
				    while (p) {
					    if (p._leaflet) {
						    close = false;
						    break;
					    }
					    p = p.parentElement;
				    }
				    console.log('blur:', e, close);
				    if(close) {
					    self.closeMap();
				    }
			    });

		    self.$map.on('click', function(e) {
			    console.log('map click:', e);
		    });
		    self.$map.on('focus', function(e) {
			    console.log('map focus:', e);
		    });
		    self.$map.on('blur', function(e) {
			    console.log('map blur:', e);
		    });
		});

		return this;
	};

})(jQuery, L);
<commit_msg>Use the same protocol for tiles as for parent document (HTTP or HTTPS)<commit_after>/*
TODO
(function(factory){
    if (typeof define === "function" && define.amd) {
        define(['jquery','leaflet'], factory);
    } else if (typeof exports === 'object') {
        factory(require('jquery'), require('leaflet'));
    } else {
        factory(jQuery, L);
    }
}*/
(function(jQuery, L){

	var $ = jQuery;

	$.fn.leafletLocationPicker = function(opts, onChangeLocation) {

		var http = window.location.protocol;

		var baseClassName = 'leaflet-locpicker',
			baseLayers = {
				'OSM': http + '//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
				'SAT': http + '//otile1.mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.png'
				//TODO add more free base layers
			};

		var optsMap = {
				zoom: 14,
				center: L.latLng([ 50.08, 14.43 ]),
				zoomControl: false,
				attributionControl: false
			};

		if($.isPlainObject(opts) && $.isPlainObject(opts.map))
			optsMap = $.extend(optsMap, opts.map);

		var defaults = {
			className: baseClassName,
			location: optsMap.center,
			locationFormat: '{lat}{sep}{lng}',
			locationMarker: true,
			locationDigits: 6,
			locationSep: ',',
			activeOnMove: false,
			position: 'topright',
			layer: 'OSM',
			height: 120,
			width: 180,
			map: optsMap,
			onChangeLocation: $.noop
		};

		if($.isPlainObject(opts))
			opts = $.extend(defaults, opts);

		else if($.isFunction(opts))
			opts = $.extend(defaults, {
				onChangeLocation: opts
			});
		else
			opts = defaults;

		if($.isFunction(onChangeLocation))
			opts = $.extend(defaults, {
				onChangeLocation: onChangeLocation
			});

		function roundLocation(loc) {
			return loc ? L.latLng(
				parseFloat(loc.lat).toFixed(opts.locationDigits),
				parseFloat(loc.lng).toFixed(opts.locationDigits)
			) : loc;
		}

		function parseLocation(loc) {
			var retLoc = loc;

			switch($.type(loc)) {
				case 'string':
					var ll = loc.split(opts.locationSep);
					if(ll[0] && ll[1])
						retLoc = L.latLng(ll);
					else
						retLoc = null;
				break;
				/*
				case 'array':
					retLoc = L.latLng(loc);
				break;
				case 'object':
					var lat, lng;

					if(loc.hasOwnProperty('lat'))
						lat = loc.lat;
					else if(loc.hasOwnProperty('latitude'))
						lat = loc.latitude;

					if(loc.hasOwnProperty('lng'))
						lng = loc.lng;
					else if(loc.hasOwnProperty('lon'))
						lng = loc.lon;
					else if(loc.hasOwnProperty('longitude'))
						lng = loc.longitude;

					retLoc = L.latLng(parseFloat(lat),parseFloat(lng));
				break;
				*/
				default:
					retLoc = loc;
			}
			return roundLocation( retLoc );
		}

		function buildMap(self) {

			self.divMap = document.createElement('div');
			self.$map = $(document.createElement('div'))
				.addClass(opts.className + '-map')
				.height(opts.height)
				.width(opts.width)
				.append(self.divMap)
				.appendTo('body');

			if(self.location)
				opts.map.center = self.location;

			if(typeof opts.layer === 'string' && baseLayers[opts.layer])
				opts.map.layers = L.tileLayer(baseLayers[opts.layer]);

			else if(opts.layer instanceof L.TileLayer ||
					opts.layer instanceof L.LayerGroup )
				opts.map.layers = opts.layer;

			else
				opts.map.layers = L.tileLayer(baseLayers.OSM);

			//leaflet map
			self.map = L.map(self.divMap, opts.map)
				.addControl( L.control.zoom({position:'bottomright'}) )
				.on('click', function(e) {
					self.setLocation(e.latlng);
				});

			if(opts.activeOnMove) {
				self.map.on('move', function(e) {
					self.setLocation(e.target.getCenter());
				});
			}

			var xmap = L.control({position: 'topright'});
			xmap.onAdd = function(map) {
				var btn = L.DomUtil.create('div','leaflet-control '+opts.className+'-close');
				btn.innerHTML = '×';
				L.DomEvent
					.on(btn, 'click', L.DomEvent.stop, self)
					.on(btn, 'click', self.closeMap, self);
				return btn;
			};
			xmap.addTo(self.map);

			if(opts.locationMarker)
				self.marker = buildMarker(self.location).addTo(self.map);

			return self.$map;
		}

		function buildMarker(loc) {
			var css = 'padding:0; margin:-1px; position: absolute; outline: 1px solid #fff; box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.6);';

			return L.marker( parseLocation(loc) || L.latLng(0,0), {
				icon: L.divIcon({
					className: opts.className+'-marker',
					iconAnchor: L.point(0, 0),
					html: '
'+
							'

'+
							'

'+
							'

'+
							'

'+
						'
',
				})
			});
		}

		$(this).each(function(index, input) {
		    var self = this;

		    self.$input = $(this);

		    self.locationOri = self.$input.val();

			self.onChangeLocation = function() {
				var edata = {
					latlng: self.location,
					location: self.getLocation()
				};
				self.$input.trigger($.extend(edata, {
					type: 'changeLocation'
				}));
				opts.onChangeLocation.call(self, edata);
			};

		    self.setLocation = function(loc, nocall) {
		    	self.location = parseLocation(loc);
		    	if(self.marker)
		    		self.marker.setLatLng(loc);
		    	self.$input.data('location', self.location);
		    	self.$input.val( self.getLocation() );
		    	self.onChangeLocation();
		    };

		    self.getLocation = function() {
		    	return self.location ? L.Util.template(opts.locationFormat, {
		    		lat: self.location.lat,
		    		lng: self.location.lng,
		    		sep: opts.locationSep
		    	}) : self.location;
		    };

		    self.openMap = function() {
			    switch(opts.position) {
				    case 'bottomleft':
					    self.$map.css({
						    top: self.$input.offset().top + self.$input.height() + 6,
						    left: self.$input.offset().left
					    });
					    break;
				    case 'topright':
					    self.$map.css({
						    top: self.$input.offset().top,
						    left: self.$input.offset().left + self.$input.width() + 5
					    });
					    break;
			    }

			    self.$map.show();
			    self.map.invalidateSize();
			    self.$input.trigger('show');
		    };

		    self.closeMap = function() {
			    self.$map.hide();
			    self.$input.trigger('hide');
		    };

		    self.setLocation(self.locationOri);

		    self.$map = buildMap(self);

		    self.$input
			    .addClass(opts.className)
			    .on('focus.'+opts.className, function(e) {
				    console.log('focus:', e);
				    e.preventDefault();
				    self.openMap();
			    })
			    .on('blur.'+opts.className, function(e) {
				    e.preventDefault();
				    var p = e.relatedTarget;
				    var close = true;
				    while (p) {
					    if (p._leaflet) {
						    close = false;
						    break;
					    }
					    p = p.parentElement;
				    }
				    console.log('blur:', e, close);
				    if(close) {
					    self.closeMap();
				    }
			    });

		    self.$map.on('click', function(e) {
			    console.log('map click:', e);
		    });
		    self.$map.on('focus', function(e) {
			    console.log('map focus:', e);
		    });
		    self.$map.on('blur', function(e) {
			    console.log('map blur:', e);
		    });
		});

		return this;
	};

})(jQuery, L);
<|endoftext|>


<commit_before>/*!
 * ${copyright}
 */
sap.ui.define(['sap/ui/core/library', './HashChanger', "sap/base/Log", "sap/ui/thirdparty/URI", "sap/ui/Device"],
	function(library, HashChanger, Log, URI, Device) {
	"use strict";

	// shortcut for enum(s)
	var HistoryDirection = library.routing.HistoryDirection;

	/**
	 * Used to determine the {@link sap.ui.core.routing.HistoryDirection} of the current or a future navigation,
	 * done with a {@link sap.ui.core.routing.Router} or {@link sap.ui.core.routing.HashChanger}.
	 *
	 * ATTENTION: this class will not be accurate if someone does hash-replacement without the named classes above
	 * If you are manipulating the hash directly this class is not supported anymore.
	 *
	 * @param {sap.ui.core.routing.HashChanger} oHashChanger required, without a HashChanger this class cannot work. The class needs to be aware of the hash-changes.
	 * @public
	 * @class
	 * @alias sap.ui.core.routing.History
	 */
	var History = function(oHashChanger) {
		this._iHistoryLength = window.history.length;
		this.aHistory = [];
		this._bIsInitial = true;

		if (!Device.browser.msie) { // the state information isn't used for IE
			// because it doesn't clear the state after new hash is set
			var oState = window.history.state === null ? {} : window.history.state;

			if (typeof oState === "object") {
				History._aStateHistory.push(window.location.hash);
				oState.sap = {};
				oState.sap.history = History._aStateHistory;
				window.history.replaceState(oState, window.document.title);
			} else {
				Log.debug("Unable to determine HistoryDirection as history.state is already set: " + window.history.state, "sap.ui.core.routing.History");
			}
		}

		if (!oHashChanger) {
			Log.error("sap.ui.core.routing.History constructor was called and it did not get a hashChanger as parameter");
		}

		this._setHashChanger(oHashChanger);

		this._reset();
	};

	/**
	 * Stores the history of full hashes to compare with window.history.state
	 * @private
	 */
	History._aStateHistory = [];

	/**
	 * Detaches all events and cleans up this instance
	 */
	History.prototype.destroy = function(sNewHash) {
		this._unRegisterHashChanger();
	};

	/**
	 * Determines what the navigation direction for a newly given hash would be
	 * It will say Unknown if there is a history foo - bar (current history) - foo
	 * If you now ask for the direction of the hash "foo" you get Unknown because it might be backwards or forwards.
	 * For hash replacements, the history stack will be replaced at this position for the history.
	 * @param {string} [sNewHash] optional, if this parameter is not passed the last hashChange is taken.
	 * @returns {sap.ui.core.routing.HistoryDirection} or undefined, if no navigation has taken place yet.
	 * @public
	 */
	History.prototype.getDirection = function(sNewHash) {
		//no navigation has taken place and someone asks for a direction
		if (sNewHash !== undefined && this._bIsInitial) {
			return undefined;
		}

		if (sNewHash === undefined) {
			return this._sCurrentDirection;
		}

		return this._getDirection(sNewHash);
	};

	/**
	 * gets the previous hash in the history - if the last direction was Unknown or there was no navigation yet, undefined will be returned
	 * @returns {string} or undefined
	 * @public
	 */
	History.prototype.getPreviousHash = function() {
		return this.aHistory[this.iHistoryPosition - 1];
	};

	History.prototype._setHashChanger = function(oHashChanger) {
		var aHashChangeEventsInfo;

		var bDirectInstance = (Object.getPrototypeOf(oHashChanger) === HashChanger.prototype);

		if (bDirectInstance || oHashChanger.getRelevantEventsInfo !== HashChanger.prototype.getRelevantEventsInfo) {
			// if the oHashChanger is a direct instance of sap.ui.core.routing.HashChanger
			// or it has overwritten the getRelevantEventInfo function
			aHashChangeEventsInfo = oHashChanger.getRelevantEventsInfo();
		} else { // TODO: delete this once the getRelevantEventsInfo is implemented by the custom hash changer in FLP
			aHashChangeEventsInfo = oHashChanger.getEventNamesForHistory().map(function(sEventName) {
				return {
					name: sEventName
				};
			});
		}

		if (this._oHashChanger) {
			this._unRegisterHashChanger();
		}

		this._oHashChanger = oHashChanger;

		this._mEventListeners = {};

		aHashChangeEventsInfo.forEach(function(oEventInfo) {
			var sEventName = oEventInfo.name,
				oParamMapping = oEventInfo.paramMapping || {},
				fnListener = this._onHashChange.bind(this, oParamMapping);

			this._mEventListeners[sEventName] = fnListener;

			this._oHashChanger.attachEvent(sEventName, fnListener, this);
		}.bind(this));

		this._oHashChanger.attachEvent("hashReplaced", this._hashReplaced, this);
		this._oHashChanger.attachEvent("hashSet", this._hashSet, this);
	};

	History.prototype._unRegisterHashChanger = function() {
		if (this._mEventListeners) {
			var aEventNames = Object.keys(this._mEventListeners);

			aEventNames.forEach(function(sEventName) {
				this._oHashChanger.detachEvent(sEventName, this._mEventListeners[sEventName], this);
			}.bind(this));

			delete this._mEventListeners;
		}

		this._oHashChanger.detachEvent("hashReplaced", this._hashReplaced, this);
		this._oHashChanger.detachEvent("hashSet", this._hashSet, this);

		this._oHashChanger = null;
	};

		/**
	 * Empties the history array, and sets the instance back to the unknown state.
	 * @private
	 */
	History.prototype._reset = function() {
		this.aHistory.length = 0;
		this.iHistoryPosition = 0;
		this._bUnknown = true;

		/*
		 * if the history is reset it should always get the current hash since -
		 * if you go from the Unknown to a defined state and then back is pressed we can be sure that the direction is backwards.
		 * Because the only way from unknown to known state is a new entry in the history.
		 */
		this.aHistory[0] = this._oHashChanger.getHash();
	};

		/**
		 * Determines what the navigation direction for a newly given hash would be
		 * @param {string} sNewHash the new hash
		 * @param {boolean} bHistoryLengthIncreased if the history length has increased compared with the last check
		 * @param {boolean} bCheckHashChangerEvents Checks if the hash was set or replaced by the hashchanger. When getDirection is called by an app this has to be false.
		 * @returns {sap.ui.core.routing.HistoryDirection}
		 * @private
		 */
	History.prototype._getDirection = function(sNewHash, bHistoryLengthIncreased, bCheckHashChangerEvents) {

		//Next hash was set by the router - it has to be a new entry
		if (bCheckHashChangerEvents && this._oNextHash && this._oNextHash.sHash === sNewHash) {
			return HistoryDirection.NewEntry;
		}

		//increasing the history length will add entries but we cannot rely on this as only criteria, since the history length is capped
		if (bHistoryLengthIncreased) {
			return HistoryDirection.NewEntry;
		}

		//we have not had a direction yet and the application did not trigger navigation + the browser history does not increase
		//the user is navigating in his history but we cannot determine the direction
		if (this._bUnknown) {
			return HistoryDirection.Unknown;
		}

		//At this point we know the user pressed a native browser navigation button

		//both directions contain the same hash we don't know the direction
		if (this.aHistory[this.iHistoryPosition + 1] === sNewHash && this.aHistory[this.iHistoryPosition - 1] === sNewHash) {
			return HistoryDirection.Unknown;
		}

		if (this.aHistory[this.iHistoryPosition - 1] === sNewHash) {
			return HistoryDirection.Backwards;
		}

		if (this.aHistory[this.iHistoryPosition + 1] === sNewHash) {
			return HistoryDirection.Forwards;
		}

		//Nothing hit, return unknown since we cannot determine what happened
		return HistoryDirection.Unknown;
	};

	/**
	 * Determine HistoryDirection leveraging the full hash as in window.location.hash
	 * and window.history.state.
	 *
	 * @param {string} sHash the complete hash, same as window.location.hash
	 * @return {sap.ui.core.routing.HistoryDirection} The determined HistoryDirection
	 * @private
	 */
	History.prototype._getDirectionWithState = function(sHash) {
		var oState = window.history.state === null ? {} : window.history.state,
			bBackward,
			sDirection;

		if (typeof oState === "object") {
			if (oState.sap === undefined) {
				History._aStateHistory.push(sHash);
				oState.sap = {};
				oState.sap.history = History._aStateHistory;
				history.replaceState(oState, document.title);
				sDirection = HistoryDirection.NewEntry;
			} else {
				bBackward = oState.sap.history.every(function(sURL, index) {
					return sURL === History._aStateHistory[index];
				});

				// If the state history is identical with the history trace, it means
				// that a hashChanged event is fired without a real brower hash change.
				// In this case, the _getDirectionWithState can't be used to determine
				// the history direction and should fallback to the legacy function
				if (bBackward && oState.sap.history.length === History._aStateHistory.length) {
					sDirection = undefined;
				} else {
					sDirection = bBackward ? HistoryDirection.Backwards : HistoryDirection.Forwards;
					History._aStateHistory = oState.sap.history;
				}
			}
		} else {
			Log.debug("Unable to determine HistoryDirection as history.state is already set: " + window.history.state, "sap.ui.core.routing.History");
		}

		return sDirection;
	};

	History.prototype._onHashChange = function(oParamMapping, oEvent) {
		var sNewHashParamName = oParamMapping.newHash || "newHash",
			sOldHashParamName = oParamMapping.oldHash || "oldHash",
			sFullHashParamName = oParamMapping.fullHash || "fullHash";

		// Leverage the fullHash parameter if available
		this._hashChange(oEvent.getParameter(sNewHashParamName), oEvent.getParameter(sOldHashParamName), oEvent.getParameter(sFullHashParamName));
	};

	/**
	 * Handles a hash change and cleans up the History
	 * @private
	 */
	History.prototype._hashChange = function(sNewHash, sOldHash, sFullHash) {
		var actualHistoryLength = window.history.length,
			sDirection;

		//We don't want to record replaced hashes
		if (this._oNextHash && this._oNextHash.bWasReplaced && this._oNextHash.sHash === sNewHash) {
			//Since a replace has taken place, the current history entry is also replaced
			this.aHistory[this.iHistoryPosition] = sNewHash;

			if (sFullHash !== undefined && !Device.browser.msie && this === History.getInstance()) {
				// after the hash is replaced, the history state is cleared.
				// We need to update the last entry in _aStateHistory and save the
				// history back to the browser history state
				History._aStateHistory[History._aStateHistory.length - 1] = sFullHash;
				window.history.replaceState({
					sap: {
						history: History._aStateHistory
					}
				}, window.document.title);
			}

			this._oNextHash = null;
			//reset the direction to Unknown when hash is replaced after history is already initialized
			if (!this._bIsInitial) {
				this._sCurrentDirection = HistoryDirection.Unknown;
			}
			return;
		}

		//a navigation has taken place so the history is not initial anymore.
		this._bIsInitial = false;

		// Extended direction determination with window.history.state
		// IE 11 doesn't clear the window.history.state when new hash is set
		// therefore the state solution can't be used
		//
		// The enhancement for direction determination is only done for the global
		// instance because the window.history.state can only be used once for the
		// new entry determination. Once the window.history.state is changed, it
		// can't be used again for the same hashchange event to determine the
		// direction which is the case if additional History instance is created
		if (sFullHash && !Device.browser.msie && this === History.getInstance()) {
			sDirection = this._getDirectionWithState(sFullHash);
		}

		// if the direction can't be decided by using the state method, the fallback to the legacy method is taken
		if (!sDirection) {
			sDirection = this._getDirection(sNewHash, this._iHistoryLength < window.history.length, true);
		}

		this._sCurrentDirection = sDirection;

		// Remember the new history length, after it has been taken into account by getDirection
		this._iHistoryLength = actualHistoryLength;

		//the next hash direction was determined - set it back
		if (this._oNextHash) {
			this._oNextHash = null;
		}

		//We don't know the state of the history, don't record it set it back to unknown, since we can't say what comes up until the app navigates again
		if (sDirection === HistoryDirection.Unknown) {
			this._reset();
			return;
		}

		//We are at a known state of the history now, since we have a new entry / forwards or backwards
		this._bUnknown = false;

		//new history entry
		if (sDirection === HistoryDirection.NewEntry) {
			//new item and there where x back navigations before - remove all the forward items from the history
			if (this.iHistoryPosition + 1 < this.aHistory.length) {
				this.aHistory = this.aHistory.slice(0, this.iHistoryPosition + 1);
			}

			this.aHistory.push(sNewHash);
			this.iHistoryPosition += 1;
			return;
		}

		if (sDirection === HistoryDirection.Forwards) {
			this.iHistoryPosition++;
			return;
		}

		if (sDirection === HistoryDirection.Backwards) {
			this.iHistoryPosition--;
		}
	};

	/**
	 * Handles a hash change and cleans up the History
	 * @private
	 */
	History.prototype._hashSet = function(oEvent) {
		this._hashChangedByApp(oEvent.getParameter("sHash"), false);
	};

	/**
	 * Handles a hash change and cleans up the History
	 * @private
	 */
	History.prototype._hashReplaced = function(oEvent) {
		this._hashChangedByApp(oEvent.getParameter("sHash"), true);
	};

	/**
	 * Sets the next hash that is going to happen in the hashChange function - used to determine if the app or the browserHistory/links triggered this navigation
	 * @param {string} sNewHash
	 * @param {boolean} bWasReplaced
	 */
	History.prototype._hashChangedByApp = function(sNewHash, bWasReplaced) {
		this._oNextHash = { sHash : sNewHash, bWasReplaced : bWasReplaced };
	};

	var instance = new History(HashChanger.getInstance());

	/**
	 * @public
	 * @returns { sap.ui.core.routing.History } a global singleton that gets created as soon as the sap.ui.core.routing.History is required
	 */
	History.getInstance = function() {
		return instance;
	};

	return History;

}, /* bExport= */ true);
<commit_msg>[INTERNAL][FIX] Fix History breaking for custom HashChangers (non FLP)<commit_after>/*!
 * ${copyright}
 */
sap.ui.define(['sap/ui/core/library', './HashChanger', "sap/base/Log", "sap/ui/thirdparty/URI", "sap/ui/Device"],
	function(library, HashChanger, Log, URI, Device) {
	"use strict";

	// shortcut for enum(s)
	var HistoryDirection = library.routing.HistoryDirection;

	/**
	 * Used to determine the {@link sap.ui.core.routing.HistoryDirection} of the current or a future navigation,
	 * done with a {@link sap.ui.core.routing.Router} or {@link sap.ui.core.routing.HashChanger}.
	 *
	 * ATTENTION: this class will not be accurate if someone does hash-replacement without the named classes above
	 * If you are manipulating the hash directly this class is not supported anymore.
	 *
	 * @param {sap.ui.core.routing.HashChanger} oHashChanger required, without a HashChanger this class cannot work. The class needs to be aware of the hash-changes.
	 * @public
	 * @class
	 * @alias sap.ui.core.routing.History
	 */
	var History = function(oHashChanger) {
		this._iHistoryLength = window.history.length;
		this.aHistory = [];
		this._bIsInitial = true;

		if (!Device.browser.msie) { // the state information isn't used for IE
			// because it doesn't clear the state after new hash is set
			var oState = window.history.state === null ? {} : window.history.state;

			if (typeof oState === "object") {
				History._aStateHistory.push(window.location.hash);
				oState.sap = {};
				oState.sap.history = History._aStateHistory;
				window.history.replaceState(oState, window.document.title);
			} else {
				Log.debug("Unable to determine HistoryDirection as history.state is already set: " + window.history.state, "sap.ui.core.routing.History");
			}
		}

		if (!oHashChanger) {
			Log.error("sap.ui.core.routing.History constructor was called and it did not get a hashChanger as parameter");
		}

		this._setHashChanger(oHashChanger);

		this._reset();
	};

	/**
	 * Stores the history of full hashes to compare with window.history.state
	 * @private
	 */
	History._aStateHistory = [];

	/**
	 * Detaches all events and cleans up this instance
	 */
	History.prototype.destroy = function(sNewHash) {
		this._unRegisterHashChanger();
	};

	/**
	 * Determines what the navigation direction for a newly given hash would be
	 * It will say Unknown if there is a history foo - bar (current history) - foo
	 * If you now ask for the direction of the hash "foo" you get Unknown because it might be backwards or forwards.
	 * For hash replacements, the history stack will be replaced at this position for the history.
	 * @param {string} [sNewHash] optional, if this parameter is not passed the last hashChange is taken.
	 * @returns {sap.ui.core.routing.HistoryDirection} or undefined, if no navigation has taken place yet.
	 * @public
	 */
	History.prototype.getDirection = function(sNewHash) {
		//no navigation has taken place and someone asks for a direction
		if (sNewHash !== undefined && this._bIsInitial) {
			return undefined;
		}

		if (sNewHash === undefined) {
			return this._sCurrentDirection;
		}

		return this._getDirection(sNewHash);
	};

	/**
	 * gets the previous hash in the history - if the last direction was Unknown or there was no navigation yet, undefined will be returned
	 * @returns {string} or undefined
	 * @public
	 */
	History.prototype.getPreviousHash = function() {
		return this.aHistory[this.iHistoryPosition - 1];
	};

	History.prototype._setHashChanger = function(oHashChanger) {
		var aHashChangeEventsInfo;

		var bDirectInstance = (Object.getPrototypeOf(oHashChanger) === HashChanger.prototype);

		if (bDirectInstance || oHashChanger.getRelevantEventsInfo !== HashChanger.prototype.getRelevantEventsInfo || !oHashChanger.getEventNamesForHistory) {
			// if the oHashChanger is a direct instance of sap.ui.core.routing.HashChanger
			// or it has overwritten the getRelevantEventInfo function
			aHashChangeEventsInfo = oHashChanger.getRelevantEventsInfo();
		} else { // TODO: delete this once the getRelevantEventsInfo is implemented by the custom hash changer in FLP
			aHashChangeEventsInfo = oHashChanger.getEventNamesForHistory().map(function(sEventName) {
				return {
					name: sEventName
				};
			});
		}

		if (this._oHashChanger) {
			this._unRegisterHashChanger();
		}

		this._oHashChanger = oHashChanger;

		this._mEventListeners = {};

		aHashChangeEventsInfo.forEach(function(oEventInfo) {
			var sEventName = oEventInfo.name,
				oParamMapping = oEventInfo.paramMapping || {},
				fnListener = this._onHashChange.bind(this, oParamMapping);

			this._mEventListeners[sEventName] = fnListener;

			this._oHashChanger.attachEvent(sEventName, fnListener, this);
		}.bind(this));

		this._oHashChanger.attachEvent("hashReplaced", this._hashReplaced, this);
		this._oHashChanger.attachEvent("hashSet", this._hashSet, this);
	};

	History.prototype._unRegisterHashChanger = function() {
		if (this._mEventListeners) {
			var aEventNames = Object.keys(this._mEventListeners);

			aEventNames.forEach(function(sEventName) {
				this._oHashChanger.detachEvent(sEventName, this._mEventListeners[sEventName], this);
			}.bind(this));

			delete this._mEventListeners;
		}

		this._oHashChanger.detachEvent("hashReplaced", this._hashReplaced, this);
		this._oHashChanger.detachEvent("hashSet", this._hashSet, this);

		this._oHashChanger = null;
	};

		/**
	 * Empties the history array, and sets the instance back to the unknown state.
	 * @private
	 */
	History.prototype._reset = function() {
		this.aHistory.length = 0;
		this.iHistoryPosition = 0;
		this._bUnknown = true;

		/*
		 * if the history is reset it should always get the current hash since -
		 * if you go from the Unknown to a defined state and then back is pressed we can be sure that the direction is backwards.
		 * Because the only way from unknown to known state is a new entry in the history.
		 */
		this.aHistory[0] = this._oHashChanger.getHash();
	};

		/**
		 * Determines what the navigation direction for a newly given hash would be
		 * @param {string} sNewHash the new hash
		 * @param {boolean} bHistoryLengthIncreased if the history length has increased compared with the last check
		 * @param {boolean} bCheckHashChangerEvents Checks if the hash was set or replaced by the hashchanger. When getDirection is called by an app this has to be false.
		 * @returns {sap.ui.core.routing.HistoryDirection}
		 * @private
		 */
	History.prototype._getDirection = function(sNewHash, bHistoryLengthIncreased, bCheckHashChangerEvents) {

		//Next hash was set by the router - it has to be a new entry
		if (bCheckHashChangerEvents && this._oNextHash && this._oNextHash.sHash === sNewHash) {
			return HistoryDirection.NewEntry;
		}

		//increasing the history length will add entries but we cannot rely on this as only criteria, since the history length is capped
		if (bHistoryLengthIncreased) {
			return HistoryDirection.NewEntry;
		}

		//we have not had a direction yet and the application did not trigger navigation + the browser history does not increase
		//the user is navigating in his history but we cannot determine the direction
		if (this._bUnknown) {
			return HistoryDirection.Unknown;
		}

		//At this point we know the user pressed a native browser navigation button

		//both directions contain the same hash we don't know the direction
		if (this.aHistory[this.iHistoryPosition + 1] === sNewHash && this.aHistory[this.iHistoryPosition - 1] === sNewHash) {
			return HistoryDirection.Unknown;
		}

		if (this.aHistory[this.iHistoryPosition - 1] === sNewHash) {
			return HistoryDirection.Backwards;
		}

		if (this.aHistory[this.iHistoryPosition + 1] === sNewHash) {
			return HistoryDirection.Forwards;
		}

		//Nothing hit, return unknown since we cannot determine what happened
		return HistoryDirection.Unknown;
	};

	/**
	 * Determine HistoryDirection leveraging the full hash as in window.location.hash
	 * and window.history.state.
	 *
	 * @param {string} sHash the complete hash, same as window.location.hash
	 * @return {sap.ui.core.routing.HistoryDirection} The determined HistoryDirection
	 * @private
	 */
	History.prototype._getDirectionWithState = function(sHash) {
		var oState = window.history.state === null ? {} : window.history.state,
			bBackward,
			sDirection;

		if (typeof oState === "object") {
			if (oState.sap === undefined) {
				History._aStateHistory.push(sHash);
				oState.sap = {};
				oState.sap.history = History._aStateHistory;
				history.replaceState(oState, document.title);
				sDirection = HistoryDirection.NewEntry;
			} else {
				bBackward = oState.sap.history.every(function(sURL, index) {
					return sURL === History._aStateHistory[index];
				});

				// If the state history is identical with the history trace, it means
				// that a hashChanged event is fired without a real brower hash change.
				// In this case, the _getDirectionWithState can't be used to determine
				// the history direction and should fallback to the legacy function
				if (bBackward && oState.sap.history.length === History._aStateHistory.length) {
					sDirection = undefined;
				} else {
					sDirection = bBackward ? HistoryDirection.Backwards : HistoryDirection.Forwards;
					History._aStateHistory = oState.sap.history;
				}
			}
		} else {
			Log.debug("Unable to determine HistoryDirection as history.state is already set: " + window.history.state, "sap.ui.core.routing.History");
		}

		return sDirection;
	};

	History.prototype._onHashChange = function(oParamMapping, oEvent) {
		var sNewHashParamName = oParamMapping.newHash || "newHash",
			sOldHashParamName = oParamMapping.oldHash || "oldHash",
			sFullHashParamName = oParamMapping.fullHash || "fullHash";

		// Leverage the fullHash parameter if available
		this._hashChange(oEvent.getParameter(sNewHashParamName), oEvent.getParameter(sOldHashParamName), oEvent.getParameter(sFullHashParamName));
	};

	/**
	 * Handles a hash change and cleans up the History
	 * @private
	 */
	History.prototype._hashChange = function(sNewHash, sOldHash, sFullHash) {
		var actualHistoryLength = window.history.length,
			sDirection;

		//We don't want to record replaced hashes
		if (this._oNextHash && this._oNextHash.bWasReplaced && this._oNextHash.sHash === sNewHash) {
			//Since a replace has taken place, the current history entry is also replaced
			this.aHistory[this.iHistoryPosition] = sNewHash;

			if (sFullHash !== undefined && !Device.browser.msie && this === History.getInstance()) {
				// after the hash is replaced, the history state is cleared.
				// We need to update the last entry in _aStateHistory and save the
				// history back to the browser history state
				History._aStateHistory[History._aStateHistory.length - 1] = sFullHash;
				window.history.replaceState({
					sap: {
						history: History._aStateHistory
					}
				}, window.document.title);
			}

			this._oNextHash = null;
			//reset the direction to Unknown when hash is replaced after history is already initialized
			if (!this._bIsInitial) {
				this._sCurrentDirection = HistoryDirection.Unknown;
			}
			return;
		}

		//a navigation has taken place so the history is not initial anymore.
		this._bIsInitial = false;

		// Extended direction determination with window.history.state
		// IE 11 doesn't clear the window.history.state when new hash is set
		// therefore the state solution can't be used
		//
		// The enhancement for direction determination is only done for the global
		// instance because the window.history.state can only be used once for the
		// new entry determination. Once the window.history.state is changed, it
		// can't be used again for the same hashchange event to determine the
		// direction which is the case if additional History instance is created
		if (sFullHash && !Device.browser.msie && this === History.getInstance()) {
			sDirection = this._getDirectionWithState(sFullHash);
		}

		// if the direction can't be decided by using the state method, the fallback to the legacy method is taken
		if (!sDirection) {
			sDirection = this._getDirection(sNewHash, this._iHistoryLength < window.history.length, true);
		}

		this._sCurrentDirection = sDirection;

		// Remember the new history length, after it has been taken into account by getDirection
		this._iHistoryLength = actualHistoryLength;

		//the next hash direction was determined - set it back
		if (this._oNextHash) {
			this._oNextHash = null;
		}

		//We don't know the state of the history, don't record it set it back to unknown, since we can't say what comes up until the app navigates again
		if (sDirection === HistoryDirection.Unknown) {
			this._reset();
			return;
		}

		//We are at a known state of the history now, since we have a new entry / forwards or backwards
		this._bUnknown = false;

		//new history entry
		if (sDirection === HistoryDirection.NewEntry) {
			//new item and there where x back navigations before - remove all the forward items from the history
			if (this.iHistoryPosition + 1 < this.aHistory.length) {
				this.aHistory = this.aHistory.slice(0, this.iHistoryPosition + 1);
			}

			this.aHistory.push(sNewHash);
			this.iHistoryPosition += 1;
			return;
		}

		if (sDirection === HistoryDirection.Forwards) {
			this.iHistoryPosition++;
			return;
		}

		if (sDirection === HistoryDirection.Backwards) {
			this.iHistoryPosition--;
		}
	};

	/**
	 * Handles a hash change and cleans up the History
	 * @private
	 */
	History.prototype._hashSet = function(oEvent) {
		this._hashChangedByApp(oEvent.getParameter("sHash"), false);
	};

	/**
	 * Handles a hash change and cleans up the History
	 * @private
	 */
	History.prototype._hashReplaced = function(oEvent) {
		this._hashChangedByApp(oEvent.getParameter("sHash"), true);
	};

	/**
	 * Sets the next hash that is going to happen in the hashChange function - used to determine if the app or the browserHistory/links triggered this navigation
	 * @param {string} sNewHash
	 * @param {boolean} bWasReplaced
	 */
	History.prototype._hashChangedByApp = function(sNewHash, bWasReplaced) {
		this._oNextHash = { sHash : sNewHash, bWasReplaced : bWasReplaced };
	};

	var instance = new History(HashChanger.getInstance());

	/**
	 * @public
	 * @returns { sap.ui.core.routing.History } a global singleton that gets created as soon as the sap.ui.core.routing.History is required
	 */
	History.getInstance = function() {
		return instance;
	};

	return History;

}, /* bExport= */ true);
<|endoftext|>


<commit_before>angular.module('webble', [ 'ui.bootstrap' ])

  // Interaction controller
  .controller('InteractionCtrl', function($scope, $interval) {
    $scope.device = JSON.stringify({}, null, ' ');
    $scope.scanCount = 0;

    function scan() {
      $scope.scanCount++;
      try {
        navigator.bluetooth.requestDevice({
          filters: [{
            name: 'reelyActive'
          }]
        })
        .then(device => { $scope.device = JSON.stringify(device, null, ' '); })
        .catch(error => { $scope.device = { error: error }; });
      }
      catch(err) {
        $scope.device = { err: err.toString() };
      }
    }

    $interval(scan, 4000);
  });
<commit_msg>Error to string for debug<commit_after>angular.module('webble', [ 'ui.bootstrap' ])

  // Interaction controller
  .controller('InteractionCtrl', function($scope, $interval) {
    $scope.device = JSON.stringify({}, null, ' ');
    $scope.scanCount = 0;

    function scan() {
      $scope.scanCount++;
      try {
        navigator.bluetooth.requestDevice({
          filters: [{
            name: 'reelyActive'
          }]
        })
        .then(device => { $scope.device = JSON.stringify(device, null, ' '); })
        .catch(error => { $scope.device = { error: error.toString() }; });
      }
      catch(err) {
        $scope.device = { err: err.toString() };
      }
    }

    $interval(scan, 4000);
  });
<|endoftext|>


<commit_before>define(['override', 'jquery'], function(override, $) {
    "use strict";
    return {
        loadFirst: ['dragging', 'columnsizing'],
        init: function(grid, pluginOptions) {
            override(grid, function($super) {
                var sortColumns=[];
                function loadSettings() {
                    var sortSettings = grid.loadSetting("sorting");
                    if (sortSettings !== undefined && sortSettings !== null && sortSettings !== "") {
                        sortColumns = sortSettings;
                    }
                }
                return {
                    init: function() {
                        loadSettings();
                        $super.init();
                        this.container.on("click", ".pg-columnheader", function(event) {
                            var key = $(this).attr('data-column-key'),
                                col = grid.getColumnForKey(key),
                                direction;

                            if(sortColumns[0] && sortColumns[0].key === key) {
                                direction = sortColumns[0].direction;
                            }

                            if(direction == 'ascending') {
                                direction = 'descending';
                            } else {
                                direction = 'ascending';
                            }

                            grid.target.find('.pg-sort-ascending, .pg-sort-descending').removeClass('pg-sort-ascending pg-sort-descending');
                            $(this).addClass('pg-sort-' + direction);

                            sortColumns = [{ key: key, direction: direction, column: grid.getColumnForKey(key) }].concat(sortColumns.filter(function(e) {
                                return e.key !== key;
                            }));
                            grid.sorting.sort(sortColumns);
                            grid.saveSetting("sorting", sortColumns);
                        });

                        $(grid.dataSource).one("dataloaded", function(e) {
                            grid.sorting.sort(sortColumns);
                        });
                    },

                    renderHeaderCell: function(column, columnIdx) {
                        var header = $super.renderHeaderCell(column, columnIdx);

                        if(column.sortable === undefined || column.sortable) {
                            header.append("
");
                            header.addClass("pg-sortable");
                            if(sortColumns[0] && sortColumns[0].key === column.key) {
                                header.addClass('pg-sort-' + sortColumns[0].direction);
                            }
                        }

                        return header;
                    },

                    sorting: {
                        sort: function (columnSettings) {
                            if(typeof grid.dataSource.sort !== 'function') {
                                console.warn && console.warn("Trying to sort unsortable datasource");
                            } else {
                                grid.dataSource.sort(this.compareRow.bind(this, columnSettings), columnSettings);
                            }
                        },

                        compareRow: function(columnSettings, a, b) {
                            for(var x=0,l=columnSettings.length;x<l;x++) {
                                var setting = columnSettings[x],
                                    column = setting.column,
                                    result;

                                if(typeof column.compare === 'function') {
                                    result = column.compare(a[column.key],b[column.key]);
                                } else {
                                    result = this.compareValue(a[column.key],b[column.key]);
                                }

                                if(result !== 0) {
                                    if(setting.direction === 'descending') {
                                        result = -1 * result;
                                    }
                                    return result;
                                }
                            }
                        },

                        compareValue: function(a,b) {
                            if((a === null || a === undefined) && (b === null || b === undefined)) return 0;
                            if(a === null || a === undefined) return -1;
                            if(b === null || b === undefined) return 1;

                            if(typeof a === 'string' && typeof b === 'string') return this.compareString(a,b);

                            if(ab) return 1;
                            else return 0;
                        },

                        compareString: function(a,b) {
                            var split = /([0-9]+|.)/g,
                                isNumber = /^[0-9]+$/,
                                sA = a.toLocaleUpperCase().match(split),
                                sB = b.toLocaleUpperCase().match(split);
                            for(var x=0,l=Math.min(sA.length,sB.length);x<l;x++) {
                                var cA = sA[x], cB = sB[x];
                                if(cA.match(isNumber)) {
                                    if(!cB.match(isNumber)) {
                                        return -1;
                                    } else {
                                        var nA = parseInt(cA), nB = parseInt(cB);
                                        if(nA < nB) return -1;
                                        if(nA > nB) return 1;
                                    }
                                } else if(cB.match(isNumber)) {
                                    return 1;
                                }
                                if(sA[x] < sB[x]) return -1;
                                if(sA[x] > sB[x]) return 1;
                            }
                        }
                    }
                }
            });
        }
   };

});<commit_msg>fix : Migration Monitor<commit_after>define(['override', 'jquery'], function(override, $) {
    "use strict";

    function SortingDataSource(delegate) {
        var self = this;
        this.delegate = delegate;

        if(typeof delegate.applyFilter === 'function') {
            this.applyFilter = delegate.applyFilter.bind(delegate);
        }

        $(delegate).on("dataloaded", function(event) {
            self.reload();
            $(self).trigger("dataloaded");
        }).on("datachanged", function(event, data) {
            self.reload();
            $(self).trigger("datachanged", [data]);
        });

        if(delegate.isReady()) {
            this.reload();
        }
    }

    SortingDataSource.prototype = {
        view: null,

        isReady: function() {
            return this.view != null;
        },

        reload: function() {
            this.delegate.assertReady();
            if(this.comparator) {
                this.view = this.delegate.getData().sort(this.comparator);
            } else {
                this.view = this.delegate.getData()
            }
        },

        recordCount: function() {
            this.assertReady();
            return this.view.length;
        },

        getData: function(start, end) {
            this.assertReady();
            if(!start && !end) return this.view;
            if(!start) start = 0;
            if(!end) end = this.recordCount();
            return this.view.slice(start, end);
        },

        getValue: function(rowId, key) {
            return this.delegate.getValue(rowId, key);
        },

        setValue: function(rowId, key, value) {
            this.delegate.setValue(rowId, key, value);
        },

        assertReady: function() {
            if(!this.isReady()) throw Error("Datasource not ready yet");
        },

        buildStatistics: function() {
            return this.delegate.buildStatistics();
        },

        getRecordById: function(id) {
            return this.delegate.getRecordById(id);
        },

        sort: function(comparator) {
            this.comparator = comparator;
            this.reload();
            $(this).trigger("dataloaded");
        }
    };

    return {
        loadFirst: ['dragging', 'columnsizing'],
        init: function(grid, pluginOptions) {
            override(grid, function($super) {
                var sortColumns=[];
                function loadSettings() {
                    var sortSettings = grid.loadSetting("sorting");
                    if (sortSettings !== undefined && sortSettings !== null && sortSettings !== "") {
                        sortColumns = sortSettings;
                    }
                }
                return {
                    init: function() {
                        loadSettings();

                        if(typeof this.dataSource.sort !== 'function') {
                            this.dataSource = new SortingDataSource(this.dataSource);
                        }

                        $super.init();
                        this.container.on("click", ".pg-columnheader", function(event) {
                            var key = $(this).attr('data-column-key'),
                                col = grid.getColumnForKey(key),
                                direction;

                            if(sortColumns[0] && sortColumns[0].key === key) {
                                direction = sortColumns[0].direction;
                            }

                            if(direction == 'ascending') {
                                direction = 'descending';
                            } else {
                                direction = 'ascending';
                            }

                            grid.target.find('.pg-sort-ascending, .pg-sort-descending').removeClass('pg-sort-ascending pg-sort-descending');
                            $(this).addClass('pg-sort-' + direction);

                            sortColumns = [{ key: key, direction: direction, column: grid.getColumnForKey(key) }].concat(sortColumns.filter(function(e) {
                                return e.key !== key;
                            }));
                            grid.sorting.sort(sortColumns);
                            grid.saveSetting("sorting", sortColumns);
                        });

                        $(grid.dataSource).one("dataloaded", function(e) {
                            grid.sorting.sort(sortColumns);
                        });
                    },

                    renderHeaderCell: function(column, columnIdx) {
                        var header = $super.renderHeaderCell(column, columnIdx);

                        if(column.sortable === undefined || column.sortable) {
                            header.append("
");
                            header.addClass("pg-sortable");
                            if(sortColumns[0] && sortColumns[0].key === column.key) {
                                header.addClass('pg-sort-' + sortColumns[0].direction);
                            }
                        }

                        return header;
                    },

                    sorting: {
                        sort: function (columnSettings) {
                            if(typeof grid.dataSource.sort !== 'function') {
                                console.warn && console.warn("Trying to sort unsortable datasource");
                            } else {
                                grid.dataSource.sort(this.compareRow.bind(this, columnSettings), columnSettings);
                            }
                        },

                        compareRow: function(columnSettings, a, b) {
                            for(var x=0,l=columnSettings.length;x<l;x++) {
                                var setting = columnSettings[x],
                                    column = setting.column,
                                    result;

                                if(typeof column.compare === 'function') {
                                    result = column.compare(a[column.key],b[column.key]);
                                } else {
                                    result = this.compareValue(a[column.key],b[column.key]);
                                }

                                if(result !== 0) {
                                    if(setting.direction === 'descending') {
                                        result = -1 * result;
                                    }
                                    return result;
                                }
                            }
                        },

                        compareValue: function(a,b) {
                            if((a === null || a === undefined) && (b === null || b === undefined)) return 0;
                            if(a === null || a === undefined) return -1;
                            if(b === null || b === undefined) return 1;

                            if(typeof a === 'string' && typeof b === 'string') return this.compareString(a,b);

                            if(ab) return 1;
                            else return 0;
                        },

                        compareString: function(a,b) {
                            var split = /([0-9]+|.)/g,
                                isNumber = /^[0-9]+$/,
                                sA = a.toLocaleUpperCase().match(split),
                                sB = b.toLocaleUpperCase().match(split);
                            for(var x=0,l=Math.min(sA.length,sB.length);x<l;x++) {
                                var cA = sA[x], cB = sB[x];
                                if(cA.match(isNumber)) {
                                    if(!cB.match(isNumber)) {
                                        return -1;
                                    } else {
                                        var nA = parseInt(cA), nB = parseInt(cB);
                                        if(nA < nB) return -1;
                                        if(nA > nB) return 1;
                                    }
                                } else if(cB.match(isNumber)) {
                                    return 1;
                                }
                                if(sA[x] < sB[x]) return -1;
                                if(sA[x] > sB[x]) return 1;
                            }
                        }
                    }
                }
            });
        }
   };

});<|endoftext|>