<commit_before>// for standard
/* globals requestAnimationFrame, io, location */
// npm requires
const kbd = require('@dasilvacontin/keyboard')
const deepEqual = require('deep-equal')
const swal = require('sweetalert')
// local requires
const GameClient = require('./gameClient.js')
const GameRenderer = require('./gameRenderer.js')
const GameAudio = require('./gameAudio.js')
const engine = require('../common/engine.js')

// we need to add a couple keys to @dasilvacontin's keyboard package!
kbd.Q_KEY = 81
kbd.R_KEY = 82
kbd.W_KEY = 87
kbd.A_KEY = 65
kbd.S_KEY = 83
kbd.D_KEY = 68
// socket.io
const socket = io()
// we initialise the game
var game = new GameClient()
// init the renderer
var renderer = new GameRenderer()
// and add a reference to the on onClick callback
// for when we click on the canvas
renderer.registerOnClickCallback(onClick)
// now, the audio
const sounds = {
  hasagi: 'sounds/hasagi.mp3',
  ulti: 'sounds/ulti.mp3',
  danger: 'sounds/danger.wav'
}
// we can access all sounds via their name later.
var audio = new GameAudio(sounds, 10)

let myUsername
const myInputs = new engine.Inputs()
const myAttackInputs = new engine.AttackInputs()

// ping calculus
let lastPingTimestamp
// how many ms the server is ahead from us
let clockDiff = 0
let ping = Infinity

// ping handshaking
function startPingHandshake () {
  lastPingTimestamp = Date.now()
  socket.emit('game:ping')
}
setInterval(startPingHandshake, 250)

// socket handling
socket.on('connect', function () {
  console.log('connected to the server')

  socket.on('game:pong', (serverNow) => {
    ping = (Date.now() - lastPingTimestamp) / 2
    clockDiff = (serverNow + ping) - Date.now()
  })

  socket.on('player:getusername', () => {
    if (myUsername) {
      // If our username is already set, we bail
      return
    }
    swal({
      title: 'Introduce your name!',
      text: 'How do you want to be called as, oh brave warrior?',
      type: 'input',
      closeOnConfirm: true,
      showCancelButton: false,
      animation: 'slide-from-top'
    }, function (inputValue) {
      myUsername = "yasuo's apprentice, " + inputValue.substring(0, 20)
      socket.emit('player:setusername', myUsername)
    })
  })

  socket.on('game:init', (players, playerId) => {
    console.log('game started')
    game.onGameInit(players, playerId)
  })

  socket.on('game.tornados:update', (tornados) => {
    game.tornados = tornados
    if (tornados.length <= 0) {
      audio.play('danger')
    } else {
      audio.play('hasagi')
    }
  })

  socket.on('player:update', (player) => {
    if (player.isDead && game.isRunning) {
      audio.play('ulti')
      if (player.id === game.myPlayerId && game.isRunning) {
        swal({
          title: 'You are dead!',
          text: `Press OK to respawn...`,
          type: 'info',
          closeOnConfirm: true,
          showConfirmButton: true,
          disableButtonsOnConfirm: true
        }, function () {
          location.reload()
        })
      }
    }
    game.players[player.id] = player
  })

  socket.on('game.players:update', (players) => {
    game.players = players
  })

  socket.on('game.players:new', (player) => {
    game.onNewPlayer(player)
  })
})

function onClick (event) {
  // Binding Q to click
  myAttackInputs.Q_KEY = true
  socket.emit('player:attack', myAttackInputs, renderer.mousePos)
}

function updateInputs () {
  const oldInputs = Object.assign({}, myInputs)

  // using @dasilvacontin's keyboard package, we can update the
  // inputs of our player based on the keyboard's state
  for (let key in myInputs) {
    myInputs[key] = kbd.isKeyDown(kbd[key])
  }

  if (!deepEqual(myInputs, oldInputs)) {
    socket.emit('player:move', myInputs)

    // update our local player' inputs aproximately when the server
    // takes them into account
    const frozenInputs = Object.assign({}, myInputs)
    setTimeout(function () {
      const myPlayer = game.getMyPlayer()
      myPlayer.inputs = frozenInputs
    }, ping)
  }

  const oldAttackinputs = Object.assign({}, myAttackInputs)

  for (let key in myAttackInputs) {
    myAttackInputs[key] = kbd.isKeyDown(kbd[key])
  }
  if (!deepEqual(myAttackInputs, oldAttackinputs)) {
    socket.emit('player:attack', myAttackInputs, renderer.mousePos)
  }

  // Check for debug mode on or off
  if (kbd.isKeyDown(48)) {
    console.log('in deb')
    game.isInDebugMode = true
  } else if (kbd.isKeyDown(49)) {
    console.log('out deb')
    game.isInDebugMode = false
  }
}

// initialise last registered timestamp
let past = Date.now()
// main game loop
function gameloop () {
  requestAnimationFrame(gameloop)
  if (!game.isRunning) { return }
  const now = Date.now()
  const delta = (now - past) / 1000
  past = now
  // once we have delta calculated we update our inputs
  updateInputs()
  // and we run our game logic
  game.logic(delta)
  // packaging info to be sent to the renderer
  game.ping = ping
  game.clockDiff = clockDiff
  // render the game
  renderer.render(delta, game)
}

requestAnimationFrame(gameloop)
<commit_msg>:fire: Removing console.logs<commit_after>// for standard
/* globals requestAnimationFrame, io, location */
// npm requires
const kbd = require('@dasilvacontin/keyboard')
const deepEqual = require('deep-equal')
const swal = require('sweetalert')
// local requires
const GameClient = require('./gameClient.js')
const GameRenderer = require('./gameRenderer.js')
const GameAudio = require('./gameAudio.js')
const engine = require('../common/engine.js')

// we need to add a couple keys to @dasilvacontin's keyboard package!
kbd.Q_KEY = 81
kbd.R_KEY = 82
kbd.W_KEY = 87
kbd.A_KEY = 65
kbd.S_KEY = 83
kbd.D_KEY = 68
// socket.io
const socket = io()
// we initialise the game
var game = new GameClient()
// init the renderer
var renderer = new GameRenderer()
// and add a reference to the on onClick callback
// for when we click on the canvas
renderer.registerOnClickCallback(onClick)
// now, the audio
const sounds = {
  hasagi: 'sounds/hasagi.mp3',
  ulti: 'sounds/ulti.mp3',
  danger: 'sounds/danger.wav'
}
// we can access all sounds via their name later.
var audio = new GameAudio(sounds, 10)

let myUsername
const myInputs = new engine.Inputs()
const myAttackInputs = new engine.AttackInputs()

// ping calculus
let lastPingTimestamp
// how many ms the server is ahead from us
let clockDiff = 0
let ping = Infinity

// ping handshaking
function startPingHandshake () {
  lastPingTimestamp = Date.now()
  socket.emit('game:ping')
}
setInterval(startPingHandshake, 250)

// socket handling
socket.on('connect', function () {
  console.log('connected to the server')

  socket.on('game:pong', (serverNow) => {
    ping = (Date.now() - lastPingTimestamp) / 2
    clockDiff = (serverNow + ping) - Date.now()
  })

  socket.on('player:getusername', () => {
    if (myUsername) {
      // If our username is already set, we bail
      return
    }
    swal({
      title: 'Introduce your name!',
      text: 'How do you want to be called as, oh brave warrior?',
      type: 'input',
      closeOnConfirm: true,
      showCancelButton: false,
      animation: 'slide-from-top'
    }, function (inputValue) {
      myUsername = "yasuo's apprentice, " + inputValue.substring(0, 20)
      socket.emit('player:setusername', myUsername)
    })
  })

  socket.on('game:init', (players, playerId) => {
    console.log('game started')
    game.onGameInit(players, playerId)
  })

  socket.on('game.tornados:update', (tornados) => {
    game.tornados = tornados
    if (tornados.length <= 0) {
      audio.play('danger')
    } else {
      audio.play('hasagi')
    }
  })

  socket.on('player:update', (player) => {
    if (player.isDead && game.isRunning) {
      audio.play('ulti')
      if (player.id === game.myPlayerId && game.isRunning) {
        swal({
          title: 'You are dead!',
          text: `Press OK to respawn...`,
          type: 'info',
          closeOnConfirm: true,
          showConfirmButton: true,
          disableButtonsOnConfirm: true
        }, function () {
          location.reload()
        })
      }
    }
    game.players[player.id] = player
  })

  socket.on('game.players:update', (players) => {
    game.players = players
  })

  socket.on('game.players:new', (player) => {
    game.onNewPlayer(player)
  })
})

function onClick (event) {
  // Binding Q to click
  myAttackInputs.Q_KEY = true
  socket.emit('player:attack', myAttackInputs, renderer.mousePos)
}

function updateInputs () {
  const oldInputs = Object.assign({}, myInputs)

  // using @dasilvacontin's keyboard package, we can update the
  // inputs of our player based on the keyboard's state
  for (let key in myInputs) {
    myInputs[key] = kbd.isKeyDown(kbd[key])
  }

  if (!deepEqual(myInputs, oldInputs)) {
    socket.emit('player:move', myInputs)

    // update our local player' inputs aproximately when the server
    // takes them into account
    const frozenInputs = Object.assign({}, myInputs)
    setTimeout(function () {
      const myPlayer = game.getMyPlayer()
      myPlayer.inputs = frozenInputs
    }, ping)
  }

  const oldAttackinputs = Object.assign({}, myAttackInputs)

  for (let key in myAttackInputs) {
    myAttackInputs[key] = kbd.isKeyDown(kbd[key])
  }
  if (!deepEqual(myAttackInputs, oldAttackinputs)) {
    socket.emit('player:attack', myAttackInputs, renderer.mousePos)
  }

  // Check for debug mode on or off
  if (kbd.isKeyDown(48)) {
    game.isInDebugMode = true
  } else if (kbd.isKeyDown(49)) {
    game.isInDebugMode = false
  }
}

// initialise last registered timestamp
let past = Date.now()
// main game loop
function gameloop () {
  requestAnimationFrame(gameloop)
  if (!game.isRunning) { return }
  const now = Date.now()
  const delta = (now - past) / 1000
  past = now
  // once we have delta calculated we update our inputs
  updateInputs()
  // and we run our game logic
  game.logic(delta)
  // packaging info to be sent to the renderer
  game.ping = ping
  game.clockDiff = clockDiff
  // render the game
  renderer.render(delta, game)
}

requestAnimationFrame(gameloop)
<|endoftext|>


<commit_before>/*
 * Copyright 2016-2017 Hewlett Packard Enterprise Development Company, L.P.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
 */

define([
    'underscore',
    'jquery',
    'backbone',
    'js-whatever/js/model-any-changed-attribute-listener',
    'find/app/vent',
    'find/app/model/document-model',
    'find/app/model/promotions-collection',
    'find/app/page/search/sort-view',
    'find/app/page/search/results/results-number-view',
    'find/app/util/view-server-client',
    'find/app/util/events',
    'find/app/page/search/results/add-links-to-summary',
    'find/app/configuration',
    'find/app/util/generate-error-support-message',
    'text!find/templates/app/page/search/results/search-result-container.html',
    'text!find/templates/app/page/search/results/results-view.html',
    'text!find/templates/app/page/loading-spinner.html',
    'moment',
    'i18n!find/nls/bundle',
    'i18n!find/nls/indexes'
], function(_, $, Backbone, addChangeListener, vent, DocumentModel, PromotionsCollection, SortView, ResultsNumberView,
            viewClient, events, addLinksToSummary, configuration, generateErrorHtml, resultTemplate, html,
            loadingSpinnerTemplate, moment, i18n, i18n_indexes) {
    'use strict';

    const SCROLL_INCREMENT = 30;
    const INFINITE_SCROLL_POSITION_PIXELS = 500;

    function infiniteScroll() {
        const resultsPresent = this.documentsCollection.size() > 0 && this.fetchStrategy.validateQuery(this.queryModel);

        if (resultsPresent && this.loadingTracker.resultsFinished && !this.endOfResults) {
            this.start = this.maxResults + 1;
            this.maxResults += SCROLL_INCREMENT;

            this.loadData(true);

            events().page(this.maxResults / SCROLL_INCREMENT);
        }
    }

    return Backbone.View.extend({
        // Overridden for HoD and IDOL implementations
        getQuestionsViewConstructor: _.constant(null),

        loadingHtml: _.template(loadingSpinnerTemplate)({i18n: i18n, large: true}),
        messageTemplate: _.template('
<%-message%>
'),
        resultTemplate: _.template(resultTemplate),

        events: {
            'click .preview-mode [data-cid]:not(.answered-question)': function(e) {
                const $target = $(e.target);

                if (!$target.is('a')) {
                    const $result = $(e.currentTarget).closest('.main-results-container');

                    if ($result.hasClass('selected-document')) {
                        // disable preview mode
                        this.previewModeModel.set({document: null});
                    } else {
                        // enable/choose another preview view
                        const cid = $result.data('cid');
                        const isPromotion = $result.closest('.main-results-list').hasClass('promotions');
                        const collection = isPromotion ? this.promotionsCollection : this.documentsCollection;
                        const model = collection.get(cid);
                        this.previewModeModel.set({document: model});

                        if (!isPromotion) {
                            events().preview(collection.indexOf(model) + 1);
                        }
                    }
                }
            },
            'click .document-detail-mode [data-cid]': function(e) {
                const $target = $(e.currentTarget);
                const cid = $target.data('cid');
                const isPromotion = $target.closest('.main-results-list').hasClass('promotions');
                const collection = isPromotion ? this.promotionsCollection : this.documentsCollection;
                const model = collection.get(cid);
                vent.navigateToDetailRoute(model);
            }
        },

        initialize: function(options) {
            this.fetchStrategy = options.fetchStrategy;
            this.documentRenderer = options.documentRenderer;

            this.queryModel = options.queryModel;
            this.showPromotions = this.fetchStrategy.promotions(this.queryModel) && !options.hidePromotions;
            this.documentsCollection = options.documentsCollection;

            this.indexesCollection = options.indexesCollection;
            this.scrollModel = options.scrollModel;

            this.loadingTracker = {
                resultsFinished: true,
                promotionsFinished: true,
                questionsFinished: true
            };

            // Preview mode is enabled when a preview mode model is provided
            this.previewModeModel = options.previewModeModel;

            if (this.indexesCollection) {
                this.selectedIndexesCollection = options.queryState.selectedIndexes;
            }

            if (this.showPromotions) {
                this.promotionsCollection = new PromotionsCollection();
            }

            if (this.fetchStrategy.answers(this.queryModel)) {
                const QuestionsView = this.getQuestionsViewConstructor();

                if (QuestionsView) {
                    this.questionsView = new QuestionsView({
                        queryModel: this.queryModel,
                        loadingTracker: this.loadingTracker,
                        clearLoadingSpinner: _.bind(this.clearLoadingSpinner, this)
                    });
                }
            }

            this.sortView = new SortView({
                queryModel: this.queryModel
            });

            this.resultsNumberView = new ResultsNumberView({
                documentsCollection: this.documentsCollection
            });

            addChangeListener(this,
                this.queryModel,
                ['sort', 'autoCorrect'].concat(this.fetchStrategy.queryModelAttributes),
                this.refreshResults);

            this.infiniteScroll = _.debounce(infiniteScroll, 500, true);

            this.listenTo(this.scrollModel, 'change', function() {
                if (this.$el.is(':visible') && this.scrollModel.get('scrollTop') > this.scrollModel.get('scrollHeight') - INFINITE_SCROLL_POSITION_PIXELS - this.scrollModel.get('innerHeight')) {
                    this.infiniteScroll();
                }
            });

            if (this.previewModeModel) {
                this.listenTo(this.previewModeModel, 'change:document', this.updateSelectedDocument);
            }
        },

        render: function() {
            this.$el.html(html);

            this.$loadingSpinner = this.$('.results-view-loading')
                .html(this.loadingHtml);

            this.sortView.setElement(this.$('.sort-container')).render();
            this.resultsNumberView.setElement(this.$('.results-number-container')).render();

            if (this.questionsView) {
                this.questionsView.setElement(this.$('.main-results-content .answered-questions')).render();
            }

            if (this.showPromotions) {
                this.listenTo(this.promotionsCollection, 'add', function(model) {
                    if (this.documentRenderer.loadPromise.state() === 'resolved') {
                        this.formatResult(model, true);
                    }
                });

                this.listenTo(this.promotionsCollection, 'sync', function() {
                    this.loadingTracker.promotionsFinished = true;
                    this.clearLoadingSpinner();
                });

                // TODO: We're basically ignoring promotions errors here -- implement robust logging procedure.
                // The Find User shouldn't hear about promotions, but the way we are doing it now, the DataAdmin or
                // SysAdmin may never find out that promotions-related errors are affecting Users' searches.
                this.listenTo(this.promotionsCollection, 'error', function(collection, xhr) {
                    if (xhr.statusText !== 'abort') {
                        this.loadingTracker.promotionsFinished = true;
                        this.clearLoadingSpinner();
                    }
                });
            }

            this.listenTo(this.documentsCollection, 'add', function(model) {
                if (this.documentRenderer.loadPromise.state() === 'resolved') {
                    this.formatResult(model, false);
                }
            });

            this.listenTo(this.documentsCollection, 'sync reset', function() {
                this.loadingTracker.resultsFinished = true;
                this.clearLoadingSpinner();

                this.endOfResults = this.maxResults >= this.documentsCollection.totalResults;

                if (this.endOfResults && !this.documentsCollection.isEmpty()) {
                    this.$('.main-results-content .results')
                        .append(this.messageTemplate({message: i18n["search.noMoreResults"]}));
                } else if (this.documentsCollection.isEmpty()) {
                    this.$('.main-results-content .results')
                        .append(this.messageTemplate({message: i18n["search.noResults"]}));
                }
            });

            this.listenTo(this.documentsCollection, 'error', function(collection, xhr) {
                if (xhr.statusText !== 'abort') {
                    this.loadingTracker.resultsFinished = true;
                    this.clearLoadingSpinner();
                    this.handleError(xhr);
                }
            });

            if (this.indexesCollection) {
                this.indexesCollection.currentRequest
                    .always(function() {
                        this.refreshResults();
                    }.bind(this));
            } else {
                this.refreshResults();
            }

            if (this.entityCollection) {
                this.updateEntityHighlighting();
            }

            if (this.previewModeModel) {
                this.$('.main-results-content').addClass('preview-mode');
                this.updateSelectedDocument();
            } else {
                this.$('.main-results-content').addClass('document-detail-mode');
            }

            this.documentRenderer.loadPromise
                .done(function() {
                    this.documentsCollection.each(function(model) {
                        this.formatResult(model, false);
                    }.bind(this));

                    if (this.showPromotions) {
                        this.promotionsCollection.each(function(model) {
                            this.formatResult(model, true);
                        }.bind(this));
                    }
                }.bind(this))
                .fail(this.handleError.bind(this))
                .always(this.clearLoadingSpinner.bind(this));
        },

        refreshResults: function() {
            if (this.fetchStrategy.validateQuery(this.queryModel) && this.documentRenderer.loadPromise.state() !== 'rejected') {
                if (this.fetchStrategy.waitForIndexes(this.queryModel)) {
                    this.$loadingSpinner.addClass('hide');
                    this.$('.main-results-content .results')
                        .html(this.messageTemplate({message: i18n_indexes['search.error.noIndexes']}));
                } else {
                    this.endOfResults = false;
                    this.start = 1;
                    this.maxResults = SCROLL_INCREMENT;
                    this.loadData(false);
                    this.$('.main-results-content .promotions').empty();

                    if (this.$loadingSpinner) {
                        this.$loadingSpinner.removeClass('hide');
                    }
                    this.toggleError(false);
                    this.$('.main-results-content .results').empty();
                }
            }
        },

        clearLoadingSpinner: function() {
            const notLoading = this.documentRenderer.loadPromise.state() !== 'pending' &&
                this.loadingTracker.resultsFinished && this.loadingTracker.questionsFinished
                && this.loadingTracker.promotionsFinished || !this.showPromotions;

            if (notLoading) {
                this.$loadingSpinner.addClass('hide');
            }
        },

        updateSelectedDocument: function() {
            const documentModel = this.previewModeModel.get('document');
            this.$('.main-results-container').removeClass('selected-document');

            if (documentModel !== null) {
                this.$('.main-results-container[data-cid="' + documentModel.cid + '"]').addClass('selected-document');
            }
        },

        formatResult: function(model, isPromotion) {
            const resultHtml = isPromotion
                ? this.documentRenderer.renderPromotion(model)
                : this.documentRenderer.renderResult(model);

            const $el = isPromotion
                ? this.$('.main-results-content .promotions')
                : this.$('.main-results-content .results');

            $el.append(this.resultTemplate({
                cid: model.cid,
                content: resultHtml
            }));
        },

        generateErrorMessage: function(xhr) {
            if (xhr.responseJSON) {
                return generateErrorHtml({
                    errorDetails: xhr.responseJSON.message,
                    errorUUID: xhr.responseJSON.uuid,
                    errorLookup: xhr.responseJSON.backendErrorCode,
                    isUserError: xhr.responseJSON.isUserError
                });
            } else {
                return generateErrorHtml();
            }
        },

        handleError: function(xhr) {
            this.toggleError(true);
            this.$('.main-results-content .results-view-error')
                .html(this.generateErrorMessage(xhr));
        },

        toggleError: function(on) {
            this.$('.main-results-content .promotions').toggleClass('hide', on);
            this.$('.main-results-content .results').toggleClass('hide', on);
            this.$('.main-results-content .results-view-error').toggleClass('hide', !on);

            if (this.questionsView) {
                this.questionsView.$el.toggleClass('hide', on);
            }
        },

        loadData: function(infiniteScroll) {
            if (this.$loadingSpinner) {
                this.$loadingSpinner.removeClass('hide');
            }

            if (this.questionsView && !infiniteScroll) {
                this.questionsView.fetchData();
            }

            this.loadingTracker.resultsFinished = false;

            const requestData = _.extend({
                start: this.start,
                max_results: this.maxResults,
                sort: this.queryModel.get('sort'),
                auto_correct: this.queryModel.get('autoCorrect'),
                queryType: 'MODIFIED'
            }, this.fetchStrategy.requestParams(this.queryModel, infiniteScroll));

            if (!infiniteScroll) {
                this.documentsCollection.reset();
            }

            this.documentsCollection.fetch({
                data: requestData,
                reset: false,
                remove: !infiniteScroll,
                error: function(collection, xhr) {
                    // if returns an error remove previous models from documentsCollection
                    if (collection && xhr.statusText !== 'abort') {
                        collection.reset();
                    }
                },
                success: function() {
                    if (this.indexesCollection && this.documentsCollection.warnings && this.documentsCollection.warnings.invalidDatabases) {
                        // Invalid databases have been deleted from IDOL; mark them as such in the indexes collection
                        this.documentsCollection.warnings.invalidDatabases.forEach(function(name) {
                            const indexModel = this.indexesCollection.findWhere({name: name});

                            if (indexModel) {
                                indexModel.set('deleted', true);
                            }

                            this.selectedIndexesCollection.remove({name: name});
                        }.bind(this));
                    }
                }.bind(this)
            });

            if (!infiniteScroll && this.showPromotions) {
                this.loadingTracker.promotionsFinished = false;

                const promotionsRequestData = _.extend({
                    start: this.start,
                    max_results: this.maxResults,
                    sort: this.queryModel.get('sort'),
                    queryType: 'PROMOTIONS'
                }, this.fetchStrategy.promotionsRequestParams(this.queryModel, infiniteScroll));

                this.promotionsCollection.fetch({
                    data: promotionsRequestData,
                    reset: false
                }, this);

                // we're not scrolling, so should be a new search
                events().reset(requestData.text);
            }
        },

        remove: function() {
            this.sortView.remove();
            this.resultsNumberView.remove();

            if (this.questionsView) {
                this.questionsView.remove();
            }

            Backbone.View.prototype.remove.call(this);
        }
    });
});
<commit_msg>(DEMOS-72, CORE-4439) Ignore the click if there's a text selection in progress to prevent the preview pane toggling every time you try selecting entities. It works since the selection gets cleared before the click handler triggers in practice.<commit_after>/*
 * Copyright 2016-2017 Hewlett Packard Enterprise Development Company, L.P.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
 */

define([
    'underscore',
    'jquery',
    'backbone',
    'js-whatever/js/model-any-changed-attribute-listener',
    'find/app/vent',
    'find/app/model/document-model',
    'find/app/model/promotions-collection',
    'find/app/page/search/sort-view',
    'find/app/page/search/results/results-number-view',
    'find/app/util/view-server-client',
    'find/app/util/events',
    'find/app/page/search/results/add-links-to-summary',
    'find/app/configuration',
    'find/app/util/generate-error-support-message',
    'text!find/templates/app/page/search/results/search-result-container.html',
    'text!find/templates/app/page/search/results/results-view.html',
    'text!find/templates/app/page/loading-spinner.html',
    'moment',
    'i18n!find/nls/bundle',
    'i18n!find/nls/indexes'
], function(_, $, Backbone, addChangeListener, vent, DocumentModel, PromotionsCollection, SortView, ResultsNumberView,
            viewClient, events, addLinksToSummary, configuration, generateErrorHtml, resultTemplate, html,
            loadingSpinnerTemplate, moment, i18n, i18n_indexes) {
    'use strict';

    const SCROLL_INCREMENT = 30;
    const INFINITE_SCROLL_POSITION_PIXELS = 500;

    function infiniteScroll() {
        const resultsPresent = this.documentsCollection.size() > 0 && this.fetchStrategy.validateQuery(this.queryModel);

        if (resultsPresent && this.loadingTracker.resultsFinished && !this.endOfResults) {
            this.start = this.maxResults + 1;
            this.maxResults += SCROLL_INCREMENT;

            this.loadData(true);

            events().page(this.maxResults / SCROLL_INCREMENT);
        }
    }

    return Backbone.View.extend({
        // Overridden for HoD and IDOL implementations
        getQuestionsViewConstructor: _.constant(null),

        loadingHtml: _.template(loadingSpinnerTemplate)({i18n: i18n, large: true}),
        messageTemplate: _.template('
<%-message%>
'),
        resultTemplate: _.template(resultTemplate),

        events: {
            'click .preview-mode [data-cid]:not(.answered-question)': function(e) {
                if (String(window.getSelection()).length >= 2) {
                    // If the user is partway selecting text for selection-entity-search, we suppress the click,
                    //   otherwise the preview pane will toggle every time you try and select something.
                    return false;
                }

                const $target = $(e.target);

                if (!$target.is('a')) {
                    const $result = $(e.currentTarget).closest('.main-results-container');

                    if ($result.hasClass('selected-document')) {
                        // disable preview mode
                        this.previewModeModel.set({document: null});
                    } else {
                        // enable/choose another preview view
                        const cid = $result.data('cid');
                        const isPromotion = $result.closest('.main-results-list').hasClass('promotions');
                        const collection = isPromotion ? this.promotionsCollection : this.documentsCollection;
                        const model = collection.get(cid);
                        this.previewModeModel.set({document: model});

                        if (!isPromotion) {
                            events().preview(collection.indexOf(model) + 1);
                        }
                    }
                }
            },
            'click .document-detail-mode [data-cid]': function(e) {
                const $target = $(e.currentTarget);
                const cid = $target.data('cid');
                const isPromotion = $target.closest('.main-results-list').hasClass('promotions');
                const collection = isPromotion ? this.promotionsCollection : this.documentsCollection;
                const model = collection.get(cid);
                vent.navigateToDetailRoute(model);
            }
        },

        initialize: function(options) {
            this.fetchStrategy = options.fetchStrategy;
            this.documentRenderer = options.documentRenderer;

            this.queryModel = options.queryModel;
            this.showPromotions = this.fetchStrategy.promotions(this.queryModel) && !options.hidePromotions;
            this.documentsCollection = options.documentsCollection;

            this.indexesCollection = options.indexesCollection;
            this.scrollModel = options.scrollModel;

            this.loadingTracker = {
                resultsFinished: true,
                promotionsFinished: true,
                questionsFinished: true
            };

            // Preview mode is enabled when a preview mode model is provided
            this.previewModeModel = options.previewModeModel;

            if (this.indexesCollection) {
                this.selectedIndexesCollection = options.queryState.selectedIndexes;
            }

            if (this.showPromotions) {
                this.promotionsCollection = new PromotionsCollection();
            }

            if (this.fetchStrategy.answers(this.queryModel)) {
                const QuestionsView = this.getQuestionsViewConstructor();

                if (QuestionsView) {
                    this.questionsView = new QuestionsView({
                        queryModel: this.queryModel,
                        loadingTracker: this.loadingTracker,
                        clearLoadingSpinner: _.bind(this.clearLoadingSpinner, this)
                    });
                }
            }

            this.sortView = new SortView({
                queryModel: this.queryModel
            });

            this.resultsNumberView = new ResultsNumberView({
                documentsCollection: this.documentsCollection
            });

            addChangeListener(this,
                this.queryModel,
                ['sort', 'autoCorrect'].concat(this.fetchStrategy.queryModelAttributes),
                this.refreshResults);

            this.infiniteScroll = _.debounce(infiniteScroll, 500, true);

            this.listenTo(this.scrollModel, 'change', function() {
                if (this.$el.is(':visible') && this.scrollModel.get('scrollTop') > this.scrollModel.get('scrollHeight') - INFINITE_SCROLL_POSITION_PIXELS - this.scrollModel.get('innerHeight')) {
                    this.infiniteScroll();
                }
            });

            if (this.previewModeModel) {
                this.listenTo(this.previewModeModel, 'change:document', this.updateSelectedDocument);
            }
        },

        render: function() {
            this.$el.html(html);

            this.$loadingSpinner = this.$('.results-view-loading')
                .html(this.loadingHtml);

            this.sortView.setElement(this.$('.sort-container')).render();
            this.resultsNumberView.setElement(this.$('.results-number-container')).render();

            if (this.questionsView) {
                this.questionsView.setElement(this.$('.main-results-content .answered-questions')).render();
            }

            if (this.showPromotions) {
                this.listenTo(this.promotionsCollection, 'add', function(model) {
                    if (this.documentRenderer.loadPromise.state() === 'resolved') {
                        this.formatResult(model, true);
                    }
                });

                this.listenTo(this.promotionsCollection, 'sync', function() {
                    this.loadingTracker.promotionsFinished = true;
                    this.clearLoadingSpinner();
                });

                // TODO: We're basically ignoring promotions errors here -- implement robust logging procedure.
                // The Find User shouldn't hear about promotions, but the way we are doing it now, the DataAdmin or
                // SysAdmin may never find out that promotions-related errors are affecting Users' searches.
                this.listenTo(this.promotionsCollection, 'error', function(collection, xhr) {
                    if (xhr.statusText !== 'abort') {
                        this.loadingTracker.promotionsFinished = true;
                        this.clearLoadingSpinner();
                    }
                });
            }

            this.listenTo(this.documentsCollection, 'add', function(model) {
                if (this.documentRenderer.loadPromise.state() === 'resolved') {
                    this.formatResult(model, false);
                }
            });

            this.listenTo(this.documentsCollection, 'sync reset', function() {
                this.loadingTracker.resultsFinished = true;
                this.clearLoadingSpinner();

                this.endOfResults = this.maxResults >= this.documentsCollection.totalResults;

                if (this.endOfResults && !this.documentsCollection.isEmpty()) {
                    this.$('.main-results-content .results')
                        .append(this.messageTemplate({message: i18n["search.noMoreResults"]}));
                } else if (this.documentsCollection.isEmpty()) {
                    this.$('.main-results-content .results')
                        .append(this.messageTemplate({message: i18n["search.noResults"]}));
                }
            });

            this.listenTo(this.documentsCollection, 'error', function(collection, xhr) {
                if (xhr.statusText !== 'abort') {
                    this.loadingTracker.resultsFinished = true;
                    this.clearLoadingSpinner();
                    this.handleError(xhr);
                }
            });

            if (this.indexesCollection) {
                this.indexesCollection.currentRequest
                    .always(function() {
                        this.refreshResults();
                    }.bind(this));
            } else {
                this.refreshResults();
            }

            if (this.entityCollection) {
                this.updateEntityHighlighting();
            }

            if (this.previewModeModel) {
                this.$('.main-results-content').addClass('preview-mode');
                this.updateSelectedDocument();
            } else {
                this.$('.main-results-content').addClass('document-detail-mode');
            }

            this.documentRenderer.loadPromise
                .done(function() {
                    this.documentsCollection.each(function(model) {
                        this.formatResult(model, false);
                    }.bind(this));

                    if (this.showPromotions) {
                        this.promotionsCollection.each(function(model) {
                            this.formatResult(model, true);
                        }.bind(this));
                    }
                }.bind(this))
                .fail(this.handleError.bind(this))
                .always(this.clearLoadingSpinner.bind(this));
        },

        refreshResults: function() {
            if (this.fetchStrategy.validateQuery(this.queryModel) && this.documentRenderer.loadPromise.state() !== 'rejected') {
                if (this.fetchStrategy.waitForIndexes(this.queryModel)) {
                    this.$loadingSpinner.addClass('hide');
                    this.$('.main-results-content .results')
                        .html(this.messageTemplate({message: i18n_indexes['search.error.noIndexes']}));
                } else {
                    this.endOfResults = false;
                    this.start = 1;
                    this.maxResults = SCROLL_INCREMENT;
                    this.loadData(false);
                    this.$('.main-results-content .promotions').empty();

                    if (this.$loadingSpinner) {
                        this.$loadingSpinner.removeClass('hide');
                    }
                    this.toggleError(false);
                    this.$('.main-results-content .results').empty();
                }
            }
        },

        clearLoadingSpinner: function() {
            const notLoading = this.documentRenderer.loadPromise.state() !== 'pending' &&
                this.loadingTracker.resultsFinished && this.loadingTracker.questionsFinished
                && this.loadingTracker.promotionsFinished || !this.showPromotions;

            if (notLoading) {
                this.$loadingSpinner.addClass('hide');
            }
        },

        updateSelectedDocument: function() {
            const documentModel = this.previewModeModel.get('document');
            this.$('.main-results-container').removeClass('selected-document');

            if (documentModel !== null) {
                this.$('.main-results-container[data-cid="' + documentModel.cid + '"]').addClass('selected-document');
            }
        },

        formatResult: function(model, isPromotion) {
            const resultHtml = isPromotion
                ? this.documentRenderer.renderPromotion(model)
                : this.documentRenderer.renderResult(model);

            const $el = isPromotion
                ? this.$('.main-results-content .promotions')
                : this.$('.main-results-content .results');

            $el.append(this.resultTemplate({
                cid: model.cid,
                content: resultHtml
            }));
        },

        generateErrorMessage: function(xhr) {
            if (xhr.responseJSON) {
                return generateErrorHtml({
                    errorDetails: xhr.responseJSON.message,
                    errorUUID: xhr.responseJSON.uuid,
                    errorLookup: xhr.responseJSON.backendErrorCode,
                    isUserError: xhr.responseJSON.isUserError
                });
            } else {
                return generateErrorHtml();
            }
        },

        handleError: function(xhr) {
            this.toggleError(true);
            this.$('.main-results-content .results-view-error')
                .html(this.generateErrorMessage(xhr));
        },

        toggleError: function(on) {
            this.$('.main-results-content .promotions').toggleClass('hide', on);
            this.$('.main-results-content .results').toggleClass('hide', on);
            this.$('.main-results-content .results-view-error').toggleClass('hide', !on);

            if (this.questionsView) {
                this.questionsView.$el.toggleClass('hide', on);
            }
        },

        loadData: function(infiniteScroll) {
            if (this.$loadingSpinner) {
                this.$loadingSpinner.removeClass('hide');
            }

            if (this.questionsView && !infiniteScroll) {
                this.questionsView.fetchData();
            }

            this.loadingTracker.resultsFinished = false;

            const requestData = _.extend({
                start: this.start,
                max_results: this.maxResults,
                sort: this.queryModel.get('sort'),
                auto_correct: this.queryModel.get('autoCorrect'),
                queryType: 'MODIFIED'
            }, this.fetchStrategy.requestParams(this.queryModel, infiniteScroll));

            if (!infiniteScroll) {
                this.documentsCollection.reset();
            }

            this.documentsCollection.fetch({
                data: requestData,
                reset: false,
                remove: !infiniteScroll,
                error: function(collection, xhr) {
                    // if returns an error remove previous models from documentsCollection
                    if (collection && xhr.statusText !== 'abort') {
                        collection.reset();
                    }
                },
                success: function() {
                    if (this.indexesCollection && this.documentsCollection.warnings && this.documentsCollection.warnings.invalidDatabases) {
                        // Invalid databases have been deleted from IDOL; mark them as such in the indexes collection
                        this.documentsCollection.warnings.invalidDatabases.forEach(function(name) {
                            const indexModel = this.indexesCollection.findWhere({name: name});

                            if (indexModel) {
                                indexModel.set('deleted', true);
                            }

                            this.selectedIndexesCollection.remove({name: name});
                        }.bind(this));
                    }
                }.bind(this)
            });

            if (!infiniteScroll && this.showPromotions) {
                this.loadingTracker.promotionsFinished = false;

                const promotionsRequestData = _.extend({
                    start: this.start,
                    max_results: this.maxResults,
                    sort: this.queryModel.get('sort'),
                    queryType: 'PROMOTIONS'
                }, this.fetchStrategy.promotionsRequestParams(this.queryModel, infiniteScroll));

                this.promotionsCollection.fetch({
                    data: promotionsRequestData,
                    reset: false
                }, this);

                // we're not scrolling, so should be a new search
                events().reset(requestData.text);
            }
        },

        remove: function() {
            this.sortView.remove();
            this.resultsNumberView.remove();

            if (this.questionsView) {
                this.questionsView.remove();
            }

            Backbone.View.prototype.remove.call(this);
        }
    });
});
<|endoftext|>


<commit_before>L.Control.Elevation = L.Control.extend({
	options: {
		position: "topright",
		theme: "lime-theme",
		width: 600,
		height: 175,
		margins: {
			top: 10,
			right: 20,
			bottom: 30,
			left: 50
		},
		useHeightIndicator: true,
		interpolation: "linear",
		hoverNumber: {
			decimalsX: 3,
			decimalsY: 0,
			formatter: undefined
		},
		xTicks: undefined,
		yTicks: undefined,
		collapsed: false
	},

	onRemove: function(map) {
		this._container = null;
	},

	onAdd: function(map) {
		this._map = map;

		var opts = this.options;
		var margin = opts.margins;
		opts.xTicks = opts.xTicks || Math.round(this._width() / 75);
		opts.yTicks = opts.yTicks || Math.round(this._height() / 30);
		opts.hoverNumber.formatter = opts.hoverNumber.formatter || this._formatter;

		//append theme name on body
		d3.select("body").classed(opts.theme, true);

		var x = this._x = d3.scale.linear()
			.range([0, this._width()]);

		var y = this._y = d3.scale.linear()
			.range([this._height(), 0]);

		var area = this._area = d3.svg.area()
			.interpolate(opts.interpolation)
			.x(function(d) {
			return x(d.dist);
		})
			.y0(this._height())
			.y1(function(d) {
			return y(d.altitude);
		});

		var container = this._container = L.DomUtil.create("div", "elevation");

		this._initToggle();

		var cont = d3.select(container);
		cont.attr("width", opts.width);
		var svg = cont.append("svg");
		svg.attr("width", opts.width)
			.attr("class", "background")
			.attr("height", opts.height)
			.append("g")
			.attr("transform", "translate(" + margin.left + "," + margin.top + ")");

		var line = d3.svg.line();
		line = line
			.x(function(d) {
			return d3.mouse(svg.select("g"))[0];
		})
			.y(function(d) {
			return this._height();
		});

		var g = d3.select(this._container).select("svg").select("g");

		this._areapath = g.append("path")
			.attr("class", "area");

		var background = this._background = g.append("rect")
			.attr("width", this._width())
			.attr("height", this._height())
			.style("fill", "none")
			.style("stroke", "none")
			.style("pointer-events", "all");

		background.on("mousemove", this._mousemoveHandler.bind(this));
		background.on("mouseout", this._mouseoutHandler.bind(this));

		this._xaxisgraphicnode = g.append("g");
		this._yaxisgraphicnode = g.append("g");
		this._appendXaxis(this._xaxisgraphicnode);
		this._appendYaxis(this._yaxisgraphicnode);

		var focusG = this._focusG = g.append("g");
		this._mousefocus = focusG.append('svg:line')
			.attr('class', 'mouse-focus-line')
			.attr('x2', '0')
			.attr('y2', '0')
			.attr('x1', '0')
			.attr('y1', '0');
		this._focuslabelX = focusG.append("svg:text")
			.style("pointer-events", "none")
			.attr("class", "mouse-focus-label-x");
		this._focuslabelY = focusG.append("svg:text")
			.style("pointer-events", "none")
			.attr("class", "mouse-focus-label-y");

		if (this._data) {
			this._applyData();
		}

		return container;
	},

	_initToggle: function () {

		/* inspired by L.Control.Layers */

		var container = this._container;

		//Makes this work on IE10 Touch devices by stopping it from firing a mouseout event when the touch is released
		container.setAttribute('aria-haspopup', true);

		if (!L.Browser.touch) {
			L.DomEvent
				.disableClickPropagation(container);
				//.disableScrollPropagation(container);
		} else {
			L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation);
		}

		if (this.options.collapsed)
		{
			this._collapse();

			if (!L.Browser.android) {
				L.DomEvent
					.on(container, 'mouseover', this._expand, this)
					.on(container, 'mouseout', this._collapse, this);
			}
			var link = this._button = L.DomUtil.create('a', 'elevation-toggle', container);
			link.href = '#';
			link.title = 'Elevation';

			if (L.Browser.touch) {
				L.DomEvent
					.on(link, 'click', L.DomEvent.stop)
					.on(link, 'click', this._expand, this);
			}
			else {
				L.DomEvent.on(link, 'focus', this._expand, this);
			}

			this._map.on('click', this._collapse, this);
			// TODO keyboard accessibility
		}
	},

	_expand: function () {
		this._container.className = this._container.className.replace(' elevation-collapsed', '');
	},

	_collapse: function () {
		L.DomUtil.addClass(this._container, 'elevation-collapsed');
	},

	_width: function () {
		var opts = this.options;
		return opts.width - opts.margins.left - opts.margins.right;
	},

	_height: function () {
		var opts = this.options;
		return opts.height - opts.margins.top - opts.margins.bottom;
	},

	/*
	 * Fromatting funciton using the given decimals and seperator
	 */
	_formatter: function(num, dec, sep) {
		var res;
		if (dec === 0) {
			res = Math.round(num) + "";
		} else {
			res = L.Util.formatNum(num, dec) + "";
		}
		var numbers = res.split(".");
		if (numbers[1]) {
			var d = dec - numbers[1].length;
			for (; d > 0; d--) {
				numbers[1] += "0";
			}
			res = numbers.join(sep || ".");
		}
		return res;
	},

	_appendYaxis: function(y) {
		y.attr("class", "y axis")
			.call(d3.svg.axis()
			.scale(this._y)
			.ticks(this.options.yTicks)
			.orient("left"))
			.append("text")
			.attr("x", 15)
			.style("text-anchor", "end")
			.text("m");
	},

	_appendXaxis: function(x) {
		x.attr("class", "x axis")
			.attr("transform", "translate(0," + this._height() + ")")
			.call(d3.svg.axis()
			.scale(this._x)
			.ticks(this.options.xTicks)
			.orient("bottom"))
			.append("text")
			.attr("x", this._width() + 15)
			.style("text-anchor", "end")
			.text("km");
	},

	_updateAxis: function() {
		this._xaxisgraphicnode.selectAll("g").remove();
		this._xaxisgraphicnode.selectAll("path").remove();
		this._xaxisgraphicnode.selectAll("text").remove();
		this._yaxisgraphicnode.selectAll("g").remove();
		this._yaxisgraphicnode.selectAll("path").remove();
		this._yaxisgraphicnode.selectAll("text").remove();
		this._appendXaxis(this._xaxisgraphicnode);
		this._appendYaxis(this._yaxisgraphicnode);
	},

	_mouseoutHandler: function() {
		if (this._marker) {
			this._map.removeLayer(this._marker);
			this._marker = null;
		}
		if (this._mouseHeightFocus) {
			this._mouseHeightFocus.style("visibility", "hidden");
			this._mouseHeightFocusLabel.style("visibility", "hidden");
		}
		if (this._pointG) {
			this._pointG.style("visibility", "hidden");
		}
		this._focusG.style("visibility", "hidden");
	},

	_mousemoveHandler: function(d, i, ctx) {
		if (!this._data || this._data.length === 0) {
			return;
		}
		var coords = d3.mouse(this._background.node());
		var opts = this.options;
		this._focusG.style("visibility", "visible");
		this._mousefocus.attr('x1', coords[0])
			.attr('y1', 0)
			.attr('x2', coords[0])
			.attr('y2', this._height())
			.classed('hidden', false);
		var bisect = d3.bisector(function(d) {
			return d.dist;
		}).left;

		var xinvert = this._x.invert(coords[0]),
			item = bisect(this._data, xinvert),
			alt = this._data[item].altitude,
			dist = this._data[item].dist,
			ll = this._data[item].latlng,
			numY = opts.hoverNumber.formatter(alt, opts.hoverNumber.decimalsY),
			numX = opts.hoverNumber.formatter(dist, opts.hoverNumber.decimalsX);

		this._focuslabelX.attr("x", coords[0])
			.text(numY + " m");
		this._focuslabelY.attr("y", this._height() - 5)
			.attr("x", coords[0])
			.text(numX + " km");

		var layerpoint = this._map.latLngToLayerPoint(ll);

		//if we use a height indicator we create one with SVG
		//otherwise we show a marker
		if (opts.useHeightIndicator) {

			if (!this._mouseHeightFocus) {

				var heightG = d3.select(".leaflet-overlay-pane svg")
					.append("g");
				this._mouseHeightFocus = heightG.append('svg:line')
					.attr('class', 'height-focus line')
					.attr('x2', '0')
					.attr('y2', '0')
					.attr('x1', '0')
					.attr('y1', '0');

				var pointG = this._pointG = heightG.append("g");
				pointG.append("svg:circle")
					.attr("r", 6)
					.attr("cx", 0)
					.attr("cy", 0)
					.attr("class", "height-focus circle-lower");

				this._mouseHeightFocusLabel = heightG.append("svg:text")
					.attr("class", "height-focus-label")
					.style("pointer-events", "none");

			}

			var normalizedAlt = this._height() / this._maxElevation * alt;
			var normalizedY = layerpoint.y - normalizedAlt;
			this._mouseHeightFocus.attr("x1", layerpoint.x)
				.attr("x2", layerpoint.x)
				.attr("y1", layerpoint.y)
				.attr("y2", normalizedY)
				.style("visibility", "visible");

			this._pointG.attr("transform", "translate(" + layerpoint.x + "," + layerpoint.y + ")")
				.style("visibility", "visible");

			this._mouseHeightFocusLabel.attr("x", layerpoint.x)
				.attr("y", normalizedY)
				.text(alt + " m")
				.style("visibility", "visible");

		} else {

			if (!this._marker) {

				this._marker = new L.Marker(ll).addTo(this._map);

			} else {

				this._marker.setLatLng(ll);

			}

		}

	},

	/*
	 * Parsing of GeoJSON data lines and their elevation in z-coordinate
	 */
	_addGeoJSONData: function(coords) {
		if (coords) {
			var data = this._data || [];
			var dist = this._dist || 0;
			var ele = this._maxElevation || 0;
			for (var i = 0; i < coords.length; i++) {
				var s = new L.LatLng(coords[i][1], coords[i][0]);
				var e = new L.LatLng(coords[i ? i - 1 : 0][1], coords[i ? i - 1 : 0][0]);
				var newdist = s.distanceTo(e);
				dist = dist + newdist / 1000;
				ele = ele < coords[i][2] ? coords[i][2] : ele;
				data.push({
					dist: dist,
					altitude: coords[i][2],
					x: coords[i][0],
					y: coords[i][1],
					latlng: s
				});
			}
			this._dist = dist;
			this._data = data;
			this._maxElevation = ele;
		}
	},

	/*
	 * Parsing function for GPX data as used by https://github.com/mpetazzoni/leaflet-gpx
	 */
	_addGPXdata: function(coords) {
		if (coords) {
			var data = this._data || [];
			var dist = this._dist || 0;
			var ele = this._maxElevation || 0;
			for (var i = 0; i < coords.length; i++) {
				var s = coords[i];
				var e = coords[i ? i - 1 : 0];
				var newdist = s.distanceTo(e);
				dist = dist + newdist / 1000;
				ele = ele < s.meta.ele ? s.meta.ele : ele;
				data.push({
					dist: dist,
					altitude: s.meta.ele,
					x: s.lng,
					y: s.lat,
					latlng: s
				});
			}
			this._dist = dist;
			this._data = data;
			this._maxElevation = ele;
		}
	},

	_addData: function(d) {
		var geom = d && d.geometry && d.geometry;
		var i;

		if (geom) {
			switch (geom.type) {
				case 'LineString':
					this._addGeoJSONData(geom.coordinates);
					break;

				case 'MultiLineString':
					for (i = 0; i < geom.coordinates.length; i++) {
						this._addGeoJSONData(geom.coordinates[i]);
					}
					break;

				default:
					throw new Error('Invalid GeoJSON object.');
			}
		}

		var feat = d && d.type === "FeatureCollection";
		if (feat) {
			for (i = 0; i < d.features.length; i++) {
				this._addData(d.features[i]);
			}
		}

		if (d && d._latlngs) {
			this._addGPXdata(d._latlngs);
		}
	},

	/*
	 * Add data to the diagram either from GPX or GeoJSON and
	 * update the axis domain and data
	 */
	addData: function(d) {
		this._addData(d);
		if (this._container) {
			this._applyData();
		}
	},

	_applyData: function() {
		var xdomain = d3.extent(this._data, function(d) {
			return d.dist;
		});
		var ydomain = d3.extent(this._data, function(d) {
			return d.altitude;
		});

		this._x.domain(xdomain);
		this._y.domain(ydomain);
		this._areapath.datum(this._data)
			.attr("d", this._area);
		this._updateAxis();
	},

	/*
	 * Reset data
	 */
	_clearData: function() {
		this._data = null;
		this._dist = null;
		this._maxElevation = null;
	},

	/*
	 * Reset data and display
	 */
	clear: function() {
		this._clearData();

		if (!this._areapath) {
			return;
		}

		// workaround for 'Error: Problem parsing d=""' in Webkit when empty data
		// https://groups.google.com/d/msg/d3-js/7rFxpXKXFhI/HzIO_NPeDuMJ
		//this._areapath.datum(this._data).attr("d", this._area);
		this._areapath.attr("d", "M0 0");

		this._x.domain([0,1]);
		this._y.domain([0,1]);
		this._updateAxis();
	},

});

L.control.elevation = function(options) {
	return new L.Control.Elevation(options);
};
<commit_msg>prepare for merge: expand tabs, width=4<commit_after>L.Control.Elevation = L.Control.extend({
    options: {
        position: "topright",
        theme: "lime-theme",
        width: 600,
        height: 175,
        margins: {
            top: 10,
            right: 20,
            bottom: 30,
            left: 50
        },
        useHeightIndicator: true,
        interpolation: "linear",
        hoverNumber: {
            decimalsX: 3,
            decimalsY: 0,
            formatter: undefined
        },
        xTicks: undefined,
        yTicks: undefined,
        collapsed: false
    },

    onRemove: function(map) {
        this._container = null;
    },

    onAdd: function(map) {
        this._map = map;

        var opts = this.options;
        var margin = opts.margins;
        opts.xTicks = opts.xTicks || Math.round(this._width() / 75);
        opts.yTicks = opts.yTicks || Math.round(this._height() / 30);
        opts.hoverNumber.formatter = opts.hoverNumber.formatter || this._formatter;

        //append theme name on body
        d3.select("body").classed(opts.theme, true);

        var x = this._x = d3.scale.linear()
            .range([0, this._width()]);

        var y = this._y = d3.scale.linear()
            .range([this._height(), 0]);

        var area = this._area = d3.svg.area()
            .interpolate(opts.interpolation)
            .x(function(d) {
            return x(d.dist);
        })
            .y0(this._height())
            .y1(function(d) {
            return y(d.altitude);
        });

        var container = this._container = L.DomUtil.create("div", "elevation");

        this._initToggle();

        var cont = d3.select(container);
        cont.attr("width", opts.width);
        var svg = cont.append("svg");
        svg.attr("width", opts.width)
            .attr("class", "background")
            .attr("height", opts.height)
            .append("g")
            .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

        var line = d3.svg.line();
        line = line
            .x(function(d) {
            return d3.mouse(svg.select("g"))[0];
        })
            .y(function(d) {
            return this._height();
        });

        var g = d3.select(this._container).select("svg").select("g");

        this._areapath = g.append("path")
            .attr("class", "area");

        var background = this._background = g.append("rect")
            .attr("width", this._width())
            .attr("height", this._height())
            .style("fill", "none")
            .style("stroke", "none")
            .style("pointer-events", "all");

        background.on("mousemove", this._mousemoveHandler.bind(this));
        background.on("mouseout", this._mouseoutHandler.bind(this));

        this._xaxisgraphicnode = g.append("g");
        this._yaxisgraphicnode = g.append("g");
        this._appendXaxis(this._xaxisgraphicnode);
        this._appendYaxis(this._yaxisgraphicnode);

        var focusG = this._focusG = g.append("g");
        this._mousefocus = focusG.append('svg:line')
            .attr('class', 'mouse-focus-line')
            .attr('x2', '0')
            .attr('y2', '0')
            .attr('x1', '0')
            .attr('y1', '0');
        this._focuslabelX = focusG.append("svg:text")
            .style("pointer-events", "none")
            .attr("class", "mouse-focus-label-x");
        this._focuslabelY = focusG.append("svg:text")
            .style("pointer-events", "none")
            .attr("class", "mouse-focus-label-y");

        if (this._data) {
            this._applyData();
        }

        return container;
    },

    _initToggle: function () {

        /* inspired by L.Control.Layers */

        var container = this._container;

        //Makes this work on IE10 Touch devices by stopping it from firing a mouseout event when the touch is released
        container.setAttribute('aria-haspopup', true);

        if (!L.Browser.touch) {
            L.DomEvent
                .disableClickPropagation(container);
                //.disableScrollPropagation(container);
        } else {
            L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation);
        }

        if (this.options.collapsed)
        {
            this._collapse();

            if (!L.Browser.android) {
                L.DomEvent
                    .on(container, 'mouseover', this._expand, this)
                    .on(container, 'mouseout', this._collapse, this);
            }
            var link = this._button = L.DomUtil.create('a', 'elevation-toggle', container);
            link.href = '#';
            link.title = 'Elevation';

            if (L.Browser.touch) {
                L.DomEvent
                    .on(link, 'click', L.DomEvent.stop)
                    .on(link, 'click', this._expand, this);
            }
            else {
                L.DomEvent.on(link, 'focus', this._expand, this);
            }

            this._map.on('click', this._collapse, this);
            // TODO keyboard accessibility
        }
    },

    _expand: function () {
        this._container.className = this._container.className.replace(' elevation-collapsed', '');
    },

    _collapse: function () {
        L.DomUtil.addClass(this._container, 'elevation-collapsed');
    },

    _width: function () {
        var opts = this.options;
        return opts.width - opts.margins.left - opts.margins.right;
    },

    _height: function () {
        var opts = this.options;
        return opts.height - opts.margins.top - opts.margins.bottom;
    },

    /*
     * Fromatting funciton using the given decimals and seperator
     */
    _formatter: function(num, dec, sep) {
        var res;
        if (dec === 0) {
            res = Math.round(num) + "";
        } else {
            res = L.Util.formatNum(num, dec) + "";
        }
        var numbers = res.split(".");
        if (numbers[1]) {
            var d = dec - numbers[1].length;
            for (; d > 0; d--) {
                numbers[1] += "0";
            }
            res = numbers.join(sep || ".");
        }
        return res;
    },

    _appendYaxis: function(y) {
        y.attr("class", "y axis")
            .call(d3.svg.axis()
            .scale(this._y)
            .ticks(this.options.yTicks)
            .orient("left"))
            .append("text")
            .attr("x", 15)
            .style("text-anchor", "end")
            .text("m");
    },

    _appendXaxis: function(x) {
        x.attr("class", "x axis")
            .attr("transform", "translate(0," + this._height() + ")")
            .call(d3.svg.axis()
            .scale(this._x)
            .ticks(this.options.xTicks)
            .orient("bottom"))
            .append("text")
            .attr("x", this._width() + 15)
            .style("text-anchor", "end")
            .text("km");
    },

    _updateAxis: function() {
        this._xaxisgraphicnode.selectAll("g").remove();
        this._xaxisgraphicnode.selectAll("path").remove();
        this._xaxisgraphicnode.selectAll("text").remove();
        this._yaxisgraphicnode.selectAll("g").remove();
        this._yaxisgraphicnode.selectAll("path").remove();
        this._yaxisgraphicnode.selectAll("text").remove();
        this._appendXaxis(this._xaxisgraphicnode);
        this._appendYaxis(this._yaxisgraphicnode);
    },

    _mouseoutHandler: function() {
        if (this._marker) {
            this._map.removeLayer(this._marker);
            this._marker = null;
        }
        if (this._mouseHeightFocus) {
            this._mouseHeightFocus.style("visibility", "hidden");
            this._mouseHeightFocusLabel.style("visibility", "hidden");
        }
        if (this._pointG) {
            this._pointG.style("visibility", "hidden");
        }
        this._focusG.style("visibility", "hidden");
    },

    _mousemoveHandler: function(d, i, ctx) {
        if (!this._data || this._data.length === 0) {
            return;
        }
        var coords = d3.mouse(this._background.node());
        var opts = this.options;
        this._focusG.style("visibility", "visible");
        this._mousefocus.attr('x1', coords[0])
            .attr('y1', 0)
            .attr('x2', coords[0])
            .attr('y2', this._height())
            .classed('hidden', false);
        var bisect = d3.bisector(function(d) {
            return d.dist;
        }).left;

        var xinvert = this._x.invert(coords[0]),
            item = bisect(this._data, xinvert),
            alt = this._data[item].altitude,
            dist = this._data[item].dist,
            ll = this._data[item].latlng,
            numY = opts.hoverNumber.formatter(alt, opts.hoverNumber.decimalsY),
            numX = opts.hoverNumber.formatter(dist, opts.hoverNumber.decimalsX);

        this._focuslabelX.attr("x", coords[0])
            .text(numY + " m");
        this._focuslabelY.attr("y", this._height() - 5)
            .attr("x", coords[0])
            .text(numX + " km");

        var layerpoint = this._map.latLngToLayerPoint(ll);

        //if we use a height indicator we create one with SVG
        //otherwise we show a marker
        if (opts.useHeightIndicator) {

            if (!this._mouseHeightFocus) {

                var heightG = d3.select(".leaflet-overlay-pane svg")
                    .append("g");
                this._mouseHeightFocus = heightG.append('svg:line')
                    .attr('class', 'height-focus line')
                    .attr('x2', '0')
                    .attr('y2', '0')
                    .attr('x1', '0')
                    .attr('y1', '0');

                var pointG = this._pointG = heightG.append("g");
                pointG.append("svg:circle")
                    .attr("r", 6)
                    .attr("cx", 0)
                    .attr("cy", 0)
                    .attr("class", "height-focus circle-lower");

                this._mouseHeightFocusLabel = heightG.append("svg:text")
                    .attr("class", "height-focus-label")
                    .style("pointer-events", "none");

            }

            var normalizedAlt = this._height() / this._maxElevation * alt;
            var normalizedY = layerpoint.y - normalizedAlt;
            this._mouseHeightFocus.attr("x1", layerpoint.x)
                .attr("x2", layerpoint.x)
                .attr("y1", layerpoint.y)
                .attr("y2", normalizedY)
                .style("visibility", "visible");

            this._pointG.attr("transform", "translate(" + layerpoint.x + "," + layerpoint.y + ")")
                .style("visibility", "visible");

            this._mouseHeightFocusLabel.attr("x", layerpoint.x)
                .attr("y", normalizedY)
                .text(alt + " m")
                .style("visibility", "visible");

        } else {

            if (!this._marker) {

                this._marker = new L.Marker(ll).addTo(this._map);

            } else {

                this._marker.setLatLng(ll);

            }

        }

    },

    /*
     * Parsing of GeoJSON data lines and their elevation in z-coordinate
     */
    _addGeoJSONData: function(coords) {
        if (coords) {
            var data = this._data || [];
            var dist = this._dist || 0;
            var ele = this._maxElevation || 0;
            for (var i = 0; i < coords.length; i++) {
                var s = new L.LatLng(coords[i][1], coords[i][0]);
                var e = new L.LatLng(coords[i ? i - 1 : 0][1], coords[i ? i - 1 : 0][0]);
                var newdist = s.distanceTo(e);
                dist = dist + newdist / 1000;
                ele = ele < coords[i][2] ? coords[i][2] : ele;
                data.push({
                    dist: dist,
                    altitude: coords[i][2],
                    x: coords[i][0],
                    y: coords[i][1],
                    latlng: s
                });
            }
            this._dist = dist;
            this._data = data;
            this._maxElevation = ele;
        }
    },

    /*
     * Parsing function for GPX data as used by https://github.com/mpetazzoni/leaflet-gpx
     */
    _addGPXdata: function(coords) {
        if (coords) {
            var data = this._data || [];
            var dist = this._dist || 0;
            var ele = this._maxElevation || 0;
            for (var i = 0; i < coords.length; i++) {
                var s = coords[i];
                var e = coords[i ? i - 1 : 0];
                var newdist = s.distanceTo(e);
                dist = dist + newdist / 1000;
                ele = ele < s.meta.ele ? s.meta.ele : ele;
                data.push({
                    dist: dist,
                    altitude: s.meta.ele,
                    x: s.lng,
                    y: s.lat,
                    latlng: s
                });
            }
            this._dist = dist;
            this._data = data;
            this._maxElevation = ele;
        }
    },

    _addData: function(d) {
        var geom = d && d.geometry && d.geometry;
        var i;

        if (geom) {
            switch (geom.type) {
                case 'LineString':
                    this._addGeoJSONData(geom.coordinates);
                    break;

                case 'MultiLineString':
                    for (i = 0; i < geom.coordinates.length; i++) {
                        this._addGeoJSONData(geom.coordinates[i]);
                    }
                    break;

                default:
                    throw new Error('Invalid GeoJSON object.');
            }
        }

        var feat = d && d.type === "FeatureCollection";
        if (feat) {
            for (i = 0; i < d.features.length; i++) {
                this._addData(d.features[i]);
            }
        }

        if (d && d._latlngs) {
            this._addGPXdata(d._latlngs);
        }
    },

    /*
     * Add data to the diagram either from GPX or GeoJSON and
     * update the axis domain and data
     */
    addData: function(d) {
        this._addData(d);
        if (this._container) {
            this._applyData();
        }
    },

    _applyData: function() {
        var xdomain = d3.extent(this._data, function(d) {
            return d.dist;
        });
        var ydomain = d3.extent(this._data, function(d) {
            return d.altitude;
        });

        this._x.domain(xdomain);
        this._y.domain(ydomain);
        this._areapath.datum(this._data)
            .attr("d", this._area);
        this._updateAxis();
    },

    /*
     * Reset data
     */
    _clearData: function() {
        this._data = null;
        this._dist = null;
        this._maxElevation = null;
    },

    /*
     * Reset data and display
     */
    clear: function() {
        this._clearData();

        if (!this._areapath) {
            return;
        }

        // workaround for 'Error: Problem parsing d=""' in Webkit when empty data
        // https://groups.google.com/d/msg/d3-js/7rFxpXKXFhI/HzIO_NPeDuMJ
        //this._areapath.datum(this._data).attr("d", this._area);
        this._areapath.attr("d", "M0 0");

        this._x.domain([0,1]);
        this._y.domain([0,1]);
        this._updateAxis();
    },

});

L.control.elevation = function(options) {
    return new L.Control.Elevation(options);
};
<|endoftext|>


<commit_before>/* globals Terraformer, L */
(function(L){

  // toggles the visibility of a layer. Used to
  // show or hide layers that move in or out of
  // the map bounds
  function setLayerVisibility(layer, visible){
    var style = (visible) ? "block" : "none";

    if(layer._icon){
      layer._icon.style.display = style;
    }

    if(layer._shadow){
      layer._shadow.style.display = style;
    }

    if(layer._layers){
      for(var layerid in layer._layers){
        if(layer._layers.hasOwnProperty(layerid)){
          layer._layers[layerid]._container.style.display = style;
        }
      }
    }
  }

  L.esri.FeatureLayer = L.GeoJSON.extend({
    includes: L.esri.Mixins.featureGrid,
    options: {
      cellSize: 512,
      debounce: 100,
      deduplicate: true
    },
    initialize: function(url, options){
      this.index = new Terraformer.RTree();
      this.url = L.esri.Util.cleanUrl(url);
      L.Util.setOptions(this, options);

      L.esri.get(this.url, {}, function(response){
        this.fire("metadata", { metadata: response });
      }, this);

      L.GeoJSON.prototype.initialize.call(this, [], options);
    },
    onAdd: function(map){
      L.LayerGroup.prototype.onAdd.call(this, map);
      map.on("zoomend resize moveEnd", this._update, this);
      this._initializeFeatureGrid(map);
    },
    onRemove: function(map){
      map.off("zoomend resize moveEnd", this._update, this);
      L.LayerGroup.prototype.onRemove.call(this, map);
      this._destroyFeatureGrid(map);
    },
    getLayerId: function(layer){
      return layer.feature.id;
    },
    _update: function(e){
      var envelope = L.esri.Util.boundsToEnvelope(e.target.getBounds());
      this.index.search(envelope, L.Util.bind(function(error,results){
        this.eachLayer(L.Util.bind(function(layer){
          var id = layer.feature.id;
          setLayerVisibility(layer, L.esri.Util.indexOf(results, id) >= 0);
        }, this));
      }, this));
    },
    _render: function(response){
      if(response.objectIdFieldName && response.features.length && !response.error){
        var idKey = response.objectIdFieldName;
        for (var i = response.features.length - 1; i >= 0; i--) {
          var feature = response.features[i];
          var id = feature.attributes[idKey];
          if(!this._layers[id]){
            var geojson = Terraformer.ArcGIS.parse(feature);
            geojson.id = id;
            this.index.insert(geojson, geojson.id);
            this.addData(geojson);
          }
        }
      }
    }
  });

  L.esri.featureLayer = function(url, options){
    return new L.esri.FeatureLayer(url, options);
  };

})(L);<commit_msg>Fixed misspelling of `moveend` map event.<commit_after>/* globals Terraformer, L */
(function(L){

  // toggles the visibility of a layer. Used to
  // show or hide layers that move in or out of
  // the map bounds
  function setLayerVisibility(layer, visible){
    var style = (visible) ? "block" : "none";

    if(layer._icon){
      layer._icon.style.display = style;
    }

    if(layer._shadow){
      layer._shadow.style.display = style;
    }

    if(layer._layers){
      for(var layerid in layer._layers){
        if(layer._layers.hasOwnProperty(layerid)){
          layer._layers[layerid]._container.style.display = style;
        }
      }
    }
  }

  L.esri.FeatureLayer = L.GeoJSON.extend({
    includes: L.esri.Mixins.featureGrid,
    options: {
      cellSize: 512,
      debounce: 100,
      deduplicate: true
    },
    initialize: function(url, options){
      this.index = new Terraformer.RTree();
      this.url = L.esri.Util.cleanUrl(url);
      L.Util.setOptions(this, options);

      L.esri.get(this.url, {}, function(response){
        this.fire("metadata", { metadata: response });
      }, this);

      L.GeoJSON.prototype.initialize.call(this, [], options);
    },
    onAdd: function(map){
      L.LayerGroup.prototype.onAdd.call(this, map);
      map.on("zoomend resize moveend", this._update, this);
      this._initializeFeatureGrid(map);
    },
    onRemove: function(map){
      map.off("zoomend resize moveend", this._update, this);
      L.LayerGroup.prototype.onRemove.call(this, map);
      this._destroyFeatureGrid(map);
    },
    getLayerId: function(layer){
      return layer.feature.id;
    },
    _update: function(e){
      var envelope = L.esri.Util.boundsToEnvelope(e.target.getBounds());
      this.index.search(envelope, L.Util.bind(function(error,results){
        this.eachLayer(L.Util.bind(function(layer){
          var id = layer.feature.id;
          setLayerVisibility(layer, L.esri.Util.indexOf(results, id) >= 0);
        }, this));
      }, this));
    },
    _render: function(response){
      if(response.objectIdFieldName && response.features.length && !response.error){
        var idKey = response.objectIdFieldName;
        for (var i = response.features.length - 1; i >= 0; i--) {
          var feature = response.features[i];
          var id = feature.attributes[idKey];
          if(!this._layers[id]){
            var geojson = Terraformer.ArcGIS.parse(feature);
            geojson.id = id;
            this.index.insert(geojson, geojson.id);
            this.addData(geojson);
          }
        }
      }
    }
  });

  L.esri.featureLayer = function(url, options){
    return new L.esri.FeatureLayer(url, options);
  };

})(L);<|endoftext|>


<commit_before>import React, {Component} from 'react';
import {elegant, sub} from 'elegant-react';
import Counter from './Counter';
import derive, {track} from './derive-decorator';
import Spring from 'react-motion';

const colors = ['#218C8D','#6CCECB','#F9E559','#EF7126','#8EDC9D','#473E3F'];
const colorCount = colors.length;

@elegant
@derive({
  @track('counts', 'sortOrder')
  items({counts, sortOrder}) {
    // map `counts` object indexes to sort order
    const order =
      counts.keySeq().sortBy(index =>
        sortOrder*counts.get(index)
      ).toKeyedSeq().flip().toObject();

    // convert to array with tweenable sortOrder property
    return counts.map( (count, index) => ({
      count,
      index,
      sortOrder: { val: order[index], config: [80,13] }
    }) ).toArray();
  }
}, true) // true enables debug mode
export default class Counters extends Component {
  render() {
    const {items,incrementActionStreams,lineHeight} = this.props;

    return

      <Spring endValue={items}>
      { tweens =>
        tweens.map((item,index) =>
            <Counter
              key={index}
              style={{ top: lineHeight * item.sortOrder.val, position: 'absolute', left: 0 }}
              value={item.count}
              color={colors[index%colorCount]}
              increment={incrementActionStreams[index]} /> )
      }</Spring>

  }
}
<commit_msg>config const<commit_after>import React, {Component} from 'react';
import {elegant, sub} from 'elegant-react';
import Counter from './Counter';
import derive, {track} from './derive-decorator';
import Spring from 'react-motion';

const colors = ['#218C8D','#6CCECB','#F9E559','#EF7126','#8EDC9D','#473E3F'];
const colorCount = colors.length;
const config = [80,13];

@elegant
@derive({
  @track('counts', 'sortOrder')
  items({counts, sortOrder}) {
    // map `counts` object indexes to sort order
    const order =
      counts.keySeq().sortBy(index =>
        sortOrder*counts.get(index)
      ).toKeyedSeq().flip().toObject();

    // convert to array with tweenable sortOrder property
    return counts.map( (count, index) => ({
      count,
      index,
      sortOrder: { val: order[index], config }
    }) ).toArray();
  }
}, true) // true enables debug mode
export default class Counters extends Component {
  render() {
    const {items,incrementActionStreams,lineHeight} = this.props;

    return

      <Spring endValue={items}>
      { tweens =>
        tweens.map((item,index) =>
            <Counter
              key={index}
              style={{ top: lineHeight * item.sortOrder.val, position: 'absolute', left: 0 }}
              value={item.count}
              color={colors[index%colorCount]}
              increment={incrementActionStreams[index]} /> )
      }</Spring>

  }
}
<|endoftext|>


<commit_before>var DataList = function (elements, options) {

    var original = [],
        keys = [],
        kvps = {},
        defaults = {
            key: null,
            sortBy: null,
            limit: null
        };

    //***********************************
    // Initialization
    //***********************************
    init();

    //***********************************
    // Define prooperties
    //***********************************
    Object.defineProperty(this, 'length', {
        get: function () { return elements.length; }
    });

    Object.defineProperty(this, 'original', {
        get: function () { return original; }
    });

    Object.defineProperty(this, 'keys', {
        get: function () { return keys; }
    });

    Object.defineProperty(this, 'values', {
        get: function () { return elements; }
    });

    Object.defineProperty(this, 'isEmpty', {
        get: function () { return !!elements.length; }
    });

    Object.defineProperty(this, 'isSimpleList', {
        get: function () { return !!options.key; }
    });

    //***********************************
    // Public API
    //***********************************
    this.sort = function (by) {

    };

    this.find = function (by) {

    };

    this.get = function (keys) {

        var k = keys,
            v = null;

        if (Array.isArray(keys)) {
            v = [];
            for (var i = 0, j = keys.length; i < j; i++) {
                v.push(kvps[keys[i]]);
            }
            return v;
        } else {
            v = kvps[k];
            if (v) { return v; }
        }
    };

    this.elementAt = function (index) {
        var k = keys[index];
        if (k) { return kvps[k]; }
    };

    this.index = function (element) {
        var i = null;
        if (this.isSimpleList) {
            i = elements.indexOf(element);
        } else {
            var k = element[options.key];
            i = keys.indexOf(k);
        }

        if (i >= 0) { return i; }
        else { return null; }
    };

    this.key = function (element) {
        if (this.isSimpleList) {
            return this.index(element);
        } else {
            return element[options.key];
        }
    };

    this.add = function (elements) {

    };

    this.remove = function (elements) {

    };

    this.contains = function (search, key) {

    };

    this.hasKey = function (key) {

    };

    this.hasKeys = function (keys) {

    };

    this.hasValue = function (element) {

    };

    this.hasValues = function (elements) {

    };

    this.clear = function () {
        elements = [];
        keys = [];
        kvps = {};
    };

    //***********************************
    // Private functions
    //***********************************
    function init() {

        options = extend(defaults, options);

        setKey(options.key);

        if (elements.length) {
            for (var i = 0, j = elements.length; i < j; i++) {
                var element = elements[i];
                original.push(element);
                if (options.key && element[options.key]) {
                    keys.push(element[options.key]);
                    kvps[options.key] = element;
                }
            }
        }
    }

    function setKey(key) {

        if (elements.length) {
            var element = element[0];

            if (key && element[key]) { options.key = k; return; }

            console.warn('"' + key + '" is not a valid `key` for DataList');

            if (element['id']) { options.key = 'id'; return; }
            if (element['Id']) { options.key = 'Id'; return; }
            if (element['ID']) { options.key = 'ID'; return; }

            console.info('Setting `key` to "' + options.key + '"');
        }
    }

    // Recursive object extend
    function extend(a, b) {
        var c = b || {};

        for (var prop in a) {
            var a_val = a[prop], b_val = b[prop];
            if (typeof a_val === 'object') {
                c[prop] = extend(a_val, b_val);
            } else {
                c[prop] = b_val || a_val;
            }
        }

        return c;
    }
}<commit_msg>Logic fixes in `get` methods<commit_after>var DataList = function (elements, options) {

    var original = [],
        keys = [],
        kvps = {},
        defaults = {
            key: null,
            sortBy: null,
            limit: null
        };

    //***********************************
    // Initialization
    //***********************************
    init();

    //***********************************
    // Define prooperties
    //***********************************
    Object.defineProperty(this, 'length', {
        get: function () { return elements.length; }
    });

    Object.defineProperty(this, 'original', {
        get: function () { return original; }
    });

    Object.defineProperty(this, 'keys', {
        get: function () { return keys; }
    });

    Object.defineProperty(this, 'values', {
        get: function () { return elements; }
    });

    Object.defineProperty(this, 'isEmpty', {
        get: function () { return !!elements.length; }
    });

    Object.defineProperty(this, 'isSimpleList', {
        get: function () { return !!options.key; }
    });

    //***********************************
    // Public API
    //***********************************
    this.sort = function (by) {

    };

    this.find = function (by) {

    };

    this.get = function (key) {

        var isArr = Array.isArray(key),
            items = this.isSimpleList ? elements : kvps;

        if (isArr && key.length > 0) {
            var v = [];
            for (var i = 0, j = key.length; i < j; i++) {
                v.push(items[key[i]]);
            }
            return v;
        } else {
            var k = isArr ? key[0] : key;
            return items[k];
        }
    };

    this.elementAt = function (index) {

        if (this.isSimpleList) {
            return elements[index];
        } else {
            return kvps[keys[index]]; }
        }
    };

    this.index = function (element) {
        var i = null;
        if (this.isSimpleList) {
            i = elements.indexOf(element);
        } else {
            var k = element[options.key];
            i = keys.indexOf(k);
        }

        if (i >= 0) { return i; }
        else { return null; }
    };

    this.key = function (element) {
        if (this.isSimpleList) {
            return this.index(element);
        } else {
            return element[options.key];
        }
    };

    this.add = function (elements) {

    };

    this.remove = function (elements) {

    };

    this.contains = function (search, key) {

    };

    this.hasKey = function (key) {

    };

    this.hasKeys = function (keys) {

    };

    this.hasValue = function (element) {

    };

    this.hasValues = function (elements) {

    };

    this.clear = function () {
        elements = [];
        keys = [];
        kvps = {};
    };

    //***********************************
    // Private functions
    //***********************************
    function init() {

        options = extend(defaults, options);

        setKey(options.key);

        if (elements.length) {
            for (var i = 0, j = elements.length; i < j; i++) {
                var element = elements[i];
                original.push(element);
                if (options.key && element[options.key]) {
                    keys.push(element[options.key]);
                    kvps[options.key] = element;
                }
            }
        }
    }

    function setKey(key) {

        if (elements.length) {
            var element = element[0];

            if (key && element[key]) { options.key = k; return; }

            console.warn('"' + key + '" is not a valid `key` for DataList');

            if (element['id']) { options.key = 'id'; return; }
            if (element['Id']) { options.key = 'Id'; return; }
            if (element['ID']) { options.key = 'ID'; return; }

            console.info('Setting `key` to "' + options.key + '"');
        }
    }

    // Recursive object extend
    function extend(a, b) {
        var c = b || {};

        for (var prop in a) {
            var a_val = a[prop], b_val = b[prop];
            if (typeof a_val === 'object') {
                c[prop] = extend(a_val, b_val);
            } else {
                c[prop] = b_val || a_val;
            }
        }

        return c;
    }
}<|endoftext|>