<commit_before>var Endpoint = require('../util/websocket-endpoint')
  , util = require('util')
  , SimpleMap = require('../shared/simple-map')
  , listUtils = require('../shared/list-utils')

module.exports = function(io) {
  return new LobbyHandler(io)
}

function LobbyHandler(io) {
  Endpoint.call(this, io, 'lobbies')

  console.log('initializing')

  this._lobbyMap = new SimpleMap()
  this._lobbies = []
  this._userLobbyMap = new SimpleMap()

  // This assumes 1 disconnect per user across all our lobbies. This is fine as long as each user
  // can only be in 1 lobby at a time, but will cause problems otherwise
  this._disconnectHandlers = new SimpleMap()
}
util.inherits(LobbyHandler, Endpoint)

LobbyHandler.prototype.subscribe = function(socket, cb) {
  socket.join('lobbies/list')
  cb(this._lobbies)
}

LobbyHandler.prototype.unsubscribe = function(socket) {
  socket.leave('lobbies/list')
}

LobbyHandler.prototype.create = function(socket, params, cb) {
  if (!params.name || params.name == 'new') {
    return cb({ msg: 'Invalid name' })
  } else if (!params.map) {
    return cb({ msg: 'Invalid map' })
  } else if (!params.size || params.size < 2 || params.size > 8) {
    return cb({ msg: 'Invalid size' })
  }

  if (this._lobbyMap.has(params.name)) {
    return cb({ msg: 'A lobby with that name already exists' })
  }

  var lobby = new Lobby(socket.handshake.userName, params.name, params.map, params.size)
  this._lobbyMap.put(lobby.name, lobby)
  this._userLobbyMap.put(socket.handshake.userId, lobby)
  listUtils.sortedInsert(this._lobbies, lobby, Lobby.compare)
  cb(null)
  this.io.sockets.in('lobbies/list').emit('lobbies/message', { action: 'create', lobby: lobby })
}

LobbyHandler.prototype.join = function(socket, params, cb) {
  var self = this
  if (!params.name || !this._lobbyMap.has(params.name)) {
    return cb({ msg: 'No lobby with that name exists' })
  } else if (this._userLobbyMap.has(socket.handshake.userId)) {
    var oldLobby = this._userLobbyMap.get(socket.handshake.userId)
    if (oldLobby.name == params.name) {
      // trying to join a lobby they're already in, simply return them the lobby info
      // TODO(tec27): reevaluate whether this is necessary/desirable once we have a lobby service on
      // the client and the whole (host creates -> then follows it up with a 'duplicate' join) thing
      // goes away
      return finish(oldLobby)
    }

    return cb({ msg: 'You cannot enter multiple lobbies at once' })
  }

  var lobby = this._lobbyMap.get(params.name)
  if (lobby.size - lobby.numPlayers < 1) {
    return cb({ msg: 'Lobby full' })
  }

  var player = new LobbyPlayer(socket.handshake.userName, 'r', false)
  this._userLobbyMap.put(socket.handshake.userId, lobby)
  var slot = lobby.addPlayer(player)

  this.io.sockets.in(lobby._socketChannel).emit('lobbies/joined/message',
      { action: 'join', slot: slot, player: player })
  return finish(lobby)

  function finish(lobby) {
    var userName = socket.handshake.userName
      , handleDisconnect = function() {
          var slot = lobby.removePlayer(userName)
          // TODO(tec27): ideally all we'd need to do is call the above; figure out how to push
          // these actions into one function call
          if (slot >= 0) {
            self.io.sockets.in(lobby._socketChannel).emit('lobbies/joined/message',
                { action: 'part', slot: slot })
          }
          self._disconnectHandlers.del(userName)
          self._userLobbyMap.del(socket.handshake.userId)
        }

    // TODO(tec27): only the last socket for this user to join this lobby matters. Ideally we only
    // let them use one socket, but I haven't quite figured out how I want to do this yet. This
    // implementation is definitely not ideal, given that old sockets stay connected, they just no
    // longer have the ability to trigger parts through disconnect.
    if (self._disconnectHandlers.has(userName)) {
      socket.removeListener('disconnect', self._disconnectHandlers.get(userName))
    }
    self._disconnectHandlers.put(userName, handleDisconnect)
    socket.on('disconnect', handleDisconnect)

    socket.join(lobby._socketChannel)
    cb(null, lobby.getFullDescription())
  }
}

// TODO(tec27): when a lobby has no players left in it, close it down. Also deal with hosts
LobbyHandler.prototype.part = function(socket, cb) {
  if (!this._userLobbyMap.has(socket.handshake.userId)) {
    return cb({ msg: 'You are not currently in a lobby' })
  }

  var lobby = this._userLobbyMap.get(socket.handshake.userId)
    , userName = socket.handshake.userName
    , slot = lobby.removePlayer(userName)

  if (this._disconnectHandlers.has(userName)) {
    socket.removeListener('disconnect', this._disconnectHandlers.get(userName))
    this._disconnectHandlers.del(userName)
  }
  this._userLobbyMap.del(socket.handshake.userId)
  if (slot < 0) {
    return cb({ msg: 'Error leaving lobby' })
  }

  socket.leave(lobby._socketChannel)
  this.io.sockets.in(lobby._socketChannel).emit('lobbies/joined/message',
      { action: 'part', slot: slot })
  cb(null)
}

LobbyHandler.prototype.chat = function(socket, params) {
  if (!params.msg || !this._userLobbyMap.has(socket.handshake.userId)) {
    return
  }

  var lobby = this._userLobbyMap.get(socket.handshake.userId)
    , userName = socket.handshake.userName
  this.io.sockets.in(lobby._socketChannel).emit('lobbies/joined/message',
      { action: 'chat', from: userName, text: params.msg })
}

LobbyHandler.prototype.startCountdown = function(socket, cb) {
  if (!this._userLobbyMap.has(socket.handshake.userId)) {
    return cb({ msg: 'You are not in a lobby' })
  }

  var lobby = this._userLobbyMap.get(socket.handshake.userId)
    , player = lobby.getPlayer(socket.handshake.userName)
  if (!player || !player.isHost) {
    return cb({ msg: 'You must be the host to start the countdown' })
  }

  var self = this
  lobby.startCountdown(function() {
    self.io.sockets.in(lobby._socketChannel).emit('lobbies/joined/message',
        { action: 'countdownComplete', host: socket.handshake.address.address, port: 6112 })
  })

  cb(null)
  self.io.sockets.in(lobby._socketChannel).emit('lobbies/joined/message',
      { action: 'countdownStarted' })
}

function Lobby(hostUsername, name, map, size) {
  this.host = hostUsername
  this.name = name
  this.map = map
  this.size = size

  Object.defineProperty(this, 'slots',
      { value: new Array(size)
      , writable: false
      , enumerable: false
      })
  Object.defineProperty(this, 'players',
      { value: []
      , writable: false
      , enumerable: false
      })
  this.players[0] = this.slots[0] = new LobbyPlayer(hostUsername, 'r', true)

  Object.defineProperty(this, 'numPlayers',
      { get: function() { return this.players.length }
      , enumerable: true
      })

  Object.defineProperty(this, '_socketChannel',
      { value: 'lobbies/joined/' + this.name
      , writable: false
      , enumerable: false
      })

  Object.defineProperty(this, '_isCountingDown',
      { value: false
      , writable: true
      , enumerable: false
      })
}

Lobby.prototype.addPlayer = function(player) {
  if (this.size - this.players.length < 1) {
    throw new Error('No space for player')
  }

  this.players.push(player)
  for (var i = 0; i < this.size; i++) {
    if (!this.slots[i]) {
      this.slots[i] = player
      return i
    }
  }
}

Lobby.prototype.removePlayer = function(playerName) {
  var i
    , len
  for (i =  0, len = this.players.length; i <  len; i++) {
    if (this.players[i].name == playerName) {
      this.players.splice(i, 1)
      break
    }
  }

  for (i = 0; i < this.size; i++) {
    if (!!this.slots[i] && this.slots[i].name == playerName) {
      this.slots[i] = undefined
      return i
    }
  }
  return -1
}

Lobby.prototype.getPlayer = function(playerName) {
  for (var i = 0; i < this.size; i++) {
    if (!!this.slots[i] && this.slots[i].name == playerName) {
      return this.slots[i]
    }
  }
  return null
}

// Returns an object with the "full" description of this lobby, for giving to people that have
// joined it instead of just viewing it on the lobby list
Lobby.prototype.getFullDescription = function() {
  return  { host: this.host
          , name: this.name
          , map: this.map
          , size: this.size
          , slots: this.slots
          , players: this.players
          }
}

Lobby.prototype.startCountdown = function(cb) {
  this._isCountingDown = true
  setTimeout(cb, 5000)
}

Lobby.compare = function(a, b) {
  return a.name.localeCompare(b.name)
}

function LobbyPlayer(name, race, isHost) {
  this.name = name
  this.race = race
  this.isHost = isHost
}
<commit_msg>Refactor lobby code to remove repeated code.<commit_after>var Emitter = require('events').EventEmitter
  , Endpoint = require('../util/websocket-endpoint')
  , SimpleMap = require('../shared/simple-map')
  , listUtils = require('../shared/list-utils')
  , util = require('util')

module.exports = function(io) {
  return new LobbyHandler(io)
}

function LobbyHandler(io) {
  Endpoint.call(this, io, 'lobbies')

  console.log('initializing')

  this._lobbyMap = new SimpleMap()
  this._lobbies = []
  this._userLobbyMap = new SimpleMap()

  // This assumes 1 disconnect per user across all our lobbies. This is fine as long as each user
  // can only be in 1 lobby at a time, but will cause problems otherwise
  this._disconnectHandlers = new SimpleMap()
}
util.inherits(LobbyHandler, Endpoint)

var LOBBY_LIST_CHANNEL = 'lobbies/list'
  , LOBBY_LIST_MESSAGE = 'lobbies/message'
LobbyHandler.prototype.subscribe = function(socket, cb) {
  socket.join(LOBBY_LIST_CHANNEL)
  cb(this._lobbies)
}

LobbyHandler.prototype.unsubscribe = function(socket) {
  socket.leave(LOBBY_LIST_CHANNEL)
}

LobbyHandler.prototype._doCreateLobby = function(host, hostUserId, name, map, size) {
  var lobby = new Lobby(host, name, map, size)
  this._lobbyMap.put(lobby.name, lobby)
  // TODO(tec27): we should probably separate the joining out from the creation here, would make the
  // following line unnecessary and just make everything a lot cleaner
  this._userLobbyMap.put(hostUserId, lobby)
  listUtils.sortedInsert(this._lobbies, lobby, Lobby.compare)
  this.io.sockets.in(LOBBY_LIST_CHANNEL)
      .emit(LOBBY_LIST_MESSAGE, { action: 'create', lobby: lobby })

  var self = this
  lobby.emitter.on('addPlayer', function onAddPlayer(slot, player) {
    self._updateJoinedLobby(lobby, { action: 'join', slot: slot, player: player })
  }).on('removePlayer', function onRemovePlayer(slot, player) {
    self._updateJoinedLobby(lobby, { action: 'part', slot: slot })
  })

  return true
}

LobbyHandler.prototype.create = function(socket, params, cb) {
  if (!params.name || params.name == 'new') {
    return cb({ msg: 'Invalid name' })
  } else if (!params.map) {
    return cb({ msg: 'Invalid map' })
  } else if (!params.size || params.size < 2 || params.size > 8) {
    return cb({ msg: 'Invalid size' })
  }

  if (this._lobbyMap.has(params.name)) {
    return cb({ msg: 'A lobby with that name already exists' })
  }

  var host = socket.handshake.userName
    , hostUserId = socket.handshake.userId
  if (this._doCreateLobby(host, hostUserId, params.name, params.map, params.size)) {
    cb(null)
  } else {
    cb({ msg: 'Error creating lobby' })
  }
}

LobbyHandler.prototype._updateJoinedLobby = function(lobby, msg) {
  this.io.sockets.in(lobby._socketChannel).emit('lobbies/joined/message', msg)
}

LobbyHandler.prototype.join = function(socket, params, cb) {
  var self = this
  if (!params.name || !this._lobbyMap.has(params.name)) {
    return cb({ msg: 'No lobby with that name exists' })
  } else if (this._userLobbyMap.has(socket.handshake.userId)) {
    var oldLobby = this._userLobbyMap.get(socket.handshake.userId)
    if (oldLobby.name == params.name) {
      // trying to join a lobby they're already in, simply return them the lobby info
      // TODO(tec27): reevaluate whether this is necessary/desirable once we have a lobby service on
      // the client and the whole (host creates -> then follows it up with a 'duplicate' join) thing
      // goes away
      return finish(oldLobby)
    }

    return cb({ msg: 'You cannot enter multiple lobbies at once' })
  }

  var lobby = this._lobbyMap.get(params.name)
  if (lobby.size - lobby.numPlayers < 1) {
    return cb({ msg: 'Lobby full' })
  }

  var player = new LobbyPlayer(socket.handshake.userName, 'r', false)
  this._userLobbyMap.put(socket.handshake.userId, lobby)
  lobby.addPlayer(player)

  return finish(lobby)

  function finish(lobby) {
    var userName = socket.handshake.userName
      , handleDisconnect = function() {
          lobby.removePlayer(userName)
          // TODO(tec27): ideally all we'd need to do is call the above; figure out how to push
          // these actions into the remove event handler
          self._disconnectHandlers.del(userName)
          self._userLobbyMap.del(socket.handshake.userId)
        }

    // TODO(tec27): only the last socket for this user to join this lobby matters. Ideally we only
    // let them use one socket, but I haven't quite figured out how I want to do this yet. This
    // implementation is definitely not ideal, given that old sockets stay connected, they just no
    // longer have the ability to trigger parts through disconnect.
    if (self._disconnectHandlers.has(userName)) {
      socket.removeListener('disconnect', self._disconnectHandlers.get(userName))
    }
    self._disconnectHandlers.put(userName, handleDisconnect)
    socket.on('disconnect', handleDisconnect)

    socket.join(lobby._socketChannel)
    cb(null, lobby.getFullDescription())
  }
}

// TODO(tec27): when a lobby has no players left in it, close it down. Also deal with hosts
LobbyHandler.prototype.part = function(socket, cb) {
  if (!this._userLobbyMap.has(socket.handshake.userId)) {
    return cb({ msg: 'You are not currently in a lobby' })
  }

  var lobby = this._userLobbyMap.get(socket.handshake.userId)
    , userName = socket.handshake.userName
    , slot = lobby.removePlayer(userName)

  if (this._disconnectHandlers.has(userName)) {
    socket.removeListener('disconnect', this._disconnectHandlers.get(userName))
    this._disconnectHandlers.del(userName)
  }
  this._userLobbyMap.del(socket.handshake.userId)
  if (slot < 0) {
    return cb({ msg: 'Error leaving lobby' })
  }

  socket.leave(lobby._socketChannel)
  cb(null)
}

LobbyHandler.prototype.chat = function(socket, params) {
  if (!params.msg || !this._userLobbyMap.has(socket.handshake.userId)) {
    return
  }

  var lobby = this._userLobbyMap.get(socket.handshake.userId)
    , userName = socket.handshake.userName
  this._updateJoinedLobby(lobby, { action: 'chat', from: userName, text: params.msg })
}

LobbyHandler.prototype.startCountdown = function(socket, cb) {
  if (!this._userLobbyMap.has(socket.handshake.userId)) {
    return cb({ msg: 'You are not in a lobby' })
  }

  var lobby = this._userLobbyMap.get(socket.handshake.userId)
    , player = lobby.getPlayer(socket.handshake.userName)
  if (!player || !player.isHost) {
    return cb({ msg: 'You must be the host to start the countdown' })
  }

  var self = this
  lobby.startCountdown(function() {
    self._updateJoinedLobby(lobby,
        { action: 'countdownComplete', host: socket.handshake.address.address, port: 6112 })
  })

  cb(null)
  this._updateJoinedLobby(lobby, { action: 'countdownStarted' })
}

function Lobby(hostUsername, name, map, size) {
  this.host = hostUsername
  this.name = name
  this.map = map
  this.size = size

  // Ideally Lobby would just be an EventEmitter itself, but it adds a bunch of properties that
  // would get stringified in JSON. To avoid that, we add it as a non-enumerable property
  Object.defineProperty(this, 'emitter',
      { value: new Emitter()
      , writable: false
      , enumerable: false
      })
  Object.defineProperty(this, 'slots',
      { value: new Array(size)
      , writable: false
      , enumerable: false
      })
  Object.defineProperty(this, 'players',
      { value: []
      , writable: false
      , enumerable: false
      })
  this.players[0] = this.slots[0] = new LobbyPlayer(hostUsername, 'r', true)

  Object.defineProperty(this, 'numPlayers',
      { get: function() { return this.players.length }
      , enumerable: true
      })

  Object.defineProperty(this, '_socketChannel',
      { value: 'lobbies/joined/' + this.name
      , writable: false
      , enumerable: false
      })

  Object.defineProperty(this, '_isCountingDown',
      { value: false
      , writable: true
      , enumerable: false
      })
}

Lobby.prototype.addPlayer = function(player) {
  if (this.size - this.players.length < 1) {
    throw new Error('No space for player')
  }

  this.players.push(player)
  for (var i = 0; i < this.size; i++) {
    if (!this.slots[i]) {
      this.slots[i] = player
      this.emitter.emit('addPlayer', i, player)
      return i
    }
  }
}

Lobby.prototype.removePlayer = function(playerName) {
  var i
    , len
  for (i =  0, len = this.players.length; i <  len; i++) {
    if (this.players[i].name == playerName) {
      this.players.splice(i, 1)
      break
    }
  }

  for (i = 0; i < this.size; i++) {
    if (!!this.slots[i] && this.slots[i].name == playerName) {
      var player = this.slots[i]
      this.slots[i] = undefined
      this.emitter.emit('removePlayer', i, player)
      return i
    }
  }
  return -1
}

Lobby.prototype.getPlayer = function(playerName) {
  for (var i = 0; i < this.size; i++) {
    if (!!this.slots[i] && this.slots[i].name == playerName) {
      return this.slots[i]
    }
  }
  return null
}

// Returns an object with the "full" description of this lobby, for giving to people that have
// joined it instead of just viewing it on the lobby list
Lobby.prototype.getFullDescription = function() {
  return  { host: this.host
          , name: this.name
          , map: this.map
          , size: this.size
          , slots: this.slots
          , players: this.players
          }
}

Lobby.prototype.startCountdown = function(cb) {
  this._isCountingDown = true
  setTimeout(cb, 5000)
}

Lobby.compare = function(a, b) {
  return a.name.localeCompare(b.name)
}

function LobbyPlayer(name, race, isHost) {
  this.name = name
  this.race = race
  this.isHost = isHost
}
<|endoftext|>


<commit_before>import Ember from 'ember';

export default Ember.Component.extend({

  tagName: 'ic-autocomplete',

  attributeBindings: [
    'is-open'
  ],

  inputValue: '',

  /**
   * Two-way bound property representing the current value.
   *
   * @property value
   * @public
   */

  value: null,

  /**
   * Becomes the placeholder of the input
   *
   * @property placeholder
   * @public
   */

  placeholder: null,

  /**
   * Attribute binding to allow styling of the component when it is open or
   * not. Style with `ic-autocomplete[is-open] {}`
   *
   * @property is-open
   * @readonly
   */

  'is-open': function() {
    return this.get('isOpen') ? 'true' : null;
  }.property('isOpen'),

  /**
   * Determines whether or not the list is open.
   *
   * @property isOpen
   * @readonly
   */

  isOpen: false,

  /**
   * Adds the cache to track all options in the list to facilitate option
   * focus, blur, and navigation, etc.
   *
   * @method createOptions
   * @private
   */

  createOptions: function() {
    this.set('options', Ember.ArrayProxy.create({content: []}));
    this.optionsMap = {};
  }.on('init'),

  /**
   * Opens the list.
   *
   * @method open
   * @public
   */

  open: function() {
    if (this.get('isOpen')) return;
    this.set('isOpen', true);
  },

  /**
   * Occasionally we want to focus the input without triggering the list to open,
   * when this is true, the focus handler will be ignored.
   *
   * @property ignoreInputFocus
   * @private
   */

  ignoreInputFocus: false,

  /**
   * Closes the popover.
   *
   * @method close
   * @public
   */

  close: function() {
    if (!this.get('isOpen')) return;
    this.set('isOpen', false);
    var focusedOption = this.get('focusedOption');
    if (focusedOption) {
      focusedOption.blur();
      // so that keyboard starts at the right place on "down" or "up"
      this.set('focusedOption', this.get('selected'));
    }
    // if the active element is an element in the component, then we want to
    // focus the input and prevent the focus from opening it again
    if (this.get('element').contains(document.activeElement)) {
      this.set('ignoreInputFocus', true);
      Ember.run.later(this, 'set', 'ignoreInputFocus', false, 0);
      this.get('input').focus();
    }
  },

  /**
   * Selects an option.
   *
   * @method selectOption
   * @param option AutocompleteOptionComponent
   * @param options Object - TODO: kill these options, should instead make
   * `selectOption` do less, and have the call sites do the other operations
   * (like focusing the input, etc).
   *   @property close Boolean - Defaults true; if false, the menu will not
   *   close after the option is selected.
   *   @property focus Boolean - Defaults true; if false, the input will not
   *   get focus.
   *   @property focusOption Boolean - Defaults true; if false, the option will
   *   not get focus.
   * @private
   */

  selectOption: function(option, options) {
    options = options || {};
    var selected = this.get('selected');
    if (selected) selected.deselect();
    this.set('selected', option);
    this.set('inputValue', this.get('selected.label'));
    option.select();
    this.focusOption(option, {focusElement: options.focusOption});
    if (options.focus !== false) {
      this.get('input').focus();
    }
    if (options.close !== false) {
      this.close();
    }
    this.sendAction('on-select', this, option);
  },

  /**
   * Handle case where apps reset the list on select, you get new options
   * all registering and therefore an infinite loop of selecting the option
   * whose value matches the current value
   *
   * TODO: DRY up with selectOption (maybe?), or maybe there's a better way
   * to prevent this when the option registers? The fact that we're doing the
   * same operation minus some side-effects is unsettling.
   *
   * @method selectOptionWithoutPotentialRecursion
   * @private
   */

  selectOptionWithoutPotentialRecursion: function(option) {
    this.set('selected', option);
    option.select();
    this.focusOption(option, {focus: false});
  },

  /**
   * @method setValueFromSelected
   * @private
   */

  setValueFromSelected: function() {
    this.set('value', this.get('selected.value'));
  }.observes('selected'),

  /**
   * Adds an option to the options cache, selects it if the value matches.
   *
   * @method registerOption
   * @private
   */

  registerOption: function(option) {
    this.get('options').pushObject(option);
    this.optionsMap[option.get('value')] = option;
    if (this.get('value') === option.get('value')) {
      if (this.get('selected')) {
        // When a new option shows up with matching attributes as the selected
        // option, select this new one instead but don't explode...  TODO: use
        // `ignoreInputFocus` or probably better, when a the selection option
        // is unregistered, clear out `selected` and then we dont' have to do
        // this dance.
        this.selectOptionWithoutPotentialRecursion(option);
      } else {
        // When an option shows up with a matching value, select it but don't
        // focus because we don't want to steal focus from the rest of the
        // page, the user isn't actually interacting with the component.
        this.selectOption(option, {focus: false});
      }
    }

  },

  /**
   * Removes an option from the options cache.
   *
   * TODO: should probably get rid of 'selected' if its selected.
   *
   * @method removeOption
   * @private
   */

  removeOption: function(optionComponent) {
    if (this.get('focusedOption') === optionComponent) {
      this.set('focusedOption', null);
    }
    this.get('options').removeObject(optionComponent);
  },

  /**
   * We don't want autocomplete to happen on backspace (feels super weird),
   * so set the `isBackspacing` property so autocomplete won't do its thing.
   *
   * @method startBackspacing
   * @private
   */

  startBackspacing: function() {
    // prevents autocomplete from not happening (yes, double negative) on
    // backspacing an empty input
    // TODO: remember what the side-effect is ...
    if (this.get('inputValue') === '') {
      return;
    }
    this.set('isBackspacing', true);
  },

  /**
   * Flow control for handling keydown.
   *
   * @property keydownMap
   * @private
   */

  keydownMap: {
    27/*esc*/:   'close',
    32/*space*/: 'maybeSelectFocusedOption',
    13/*enter*/: 'maybeSelectOnEnter',
    40/*down*/:  'focusNext',
    38/*up*/:    'focusPrevious',
    8/*backspace*/: 'startBackspacing'
  },

  /**
   * Handles keyboard interactions from all elements in the component.
   *
   * @method handleKeydown
   * @private
   */

  handleKeydown: function(event) {
    var map = this.get('keydownMap');
    var method = map[event.keyCode];
    if (this[method]) {
      return this[method](event);
    }
    var input = this.get('input');
    // After this we focus the input, but if they are using shift, we don't
    // want to actually do it (they are probably shift+tabbing away). This is a
    // blacklist of one, which makes me really nervous. We want to allow any
    // valid input character, but that's a huge whitelist, or maybe use the
    // run loop and wait for focus to settle on the new element and then decide
    // what to do.
    if (event.shiftKey) {
      return;
    }
    if (document.activeElement !== input) {
      input.focus();
      // if its not backspace, then we want to select the input, since its
      // keyDown, then on keyUp the contents will be replaced, but with
      // backspace, we dont' want to do that.
      if (event.keyCode !== 8/*backspace*/) {
        input.select();
      }
    }
  }.on('keyDown'),

  /**
   * When the user is typing we autocomplete with the label of the first option
   * in the list. The autocompleted text gets selected so the component doesn't
   * get in the way of typing.
   *
   * @method autocompleteText
   * @private
   */

  autocompleteText: function() {
    this.set('autocompletedOption', null);
    if (!this.get('isOpen') || !this.get('options.length')) {
      return;
    }
    if (this.get('isBackspacing')) {
      this.set('isBackspacing', false);
      return;
    }
    var first = this.get('options').objectAt(0);
    if (first == this.get('autocompletedOption')) {
      return;
    }
    var label = first.get('label');
    var input = this.get('inputValue');
    if (input === '') {
      return;
    }
    var fragment = label.substring(input.length);
    // since we are setting the input's value, we don't want the observers
    // doing their thing
    this.set('ignoreInputValue', true);
    // tests need this, if the body is the active element when the test starts
    // then it fails, if anything else has focus its fine :\
    this.set('ignoreInputFocus', true);
    Ember.run.later(this, 'set', 'ignoreInputFocus', false, 0);
    var el = this.get('input');
    el.value = label;
    el.setSelectionRange(input.length, label.length);
    this.set('autocompletedOption', first);
  },

  /**
   * Observes the input's value and does a bunch of stuff... including sending
   * the `on-input` action.
   *
   * @method onInput
   * @private
   */

  onInput: function() {
    if (!this.get('isOpen')) {
      this.open();
    }
    // autocomplete set it, so just bail
    if (this.get('ignoreInputValue')) {
      this.set('ignoreInputValue', false);
      return;
    }
    // we don't match anymore, clear selected, TODO: clear focused too?
    if (this.get('selected.label') !== this.get('inputValue')) {
      this.set('selected', null);
    }
    if (this.get('selected')) {
      // TODO: WHY IS THIS HERE!?
      return;
    }
    this.sendAction('on-input', this, this.get('inputValue'));
    // TODO: later because ???
    Ember.run.scheduleOnce('afterRender', this, 'autocompleteText');
  }.observes('inputValue'),

  /**
   * Focuses the next option in the list.
   *
   * @method focusNext
   * @private
   */

  focusNext: function(event) {
    event.preventDefault();
    var index = 0;
    var focusedOption = this.get('focusedOption');
    if (focusedOption) {
      index = this.get('options').indexOf(focusedOption);
      if (this.get('isOpen')) {
        index = index + 1;
      }
    }
    this.focusOptionAtIndex(index);
  },

  /**
   * Focuses the previous option in the popover.
   *
   * @method focusPrevious
   * @private
   */

  focusPrevious: function(event) {
    event.preventDefault();
    var focusedOption = this.get('focusedOption');
    if (!focusedOption) return;
    var index = this.get('options').indexOf(focusedOption);
    if (this.get('isOpen')) {
      index = index - 1;
    }
    this.focusOptionAtIndex(index);
  },

  /**
   * Focuses an option given an index in the options cache.
   *
   * @method focusOptionAtIndex
   * @private
   */

  focusOptionAtIndex: function(index) {
    var options = this.get('options');
    // loop it when at the ends
    if (index === -1) {
      index = options.get('length') - 1;
    } else if (index === options.get('length')) {
      index = 0;
    }
    var option = this.get('options').objectAt(index);
    if (!option) return;
    this.focusOption(option);
  },

  /**
   * Sets the option as the `focusedOption`
   *
   * @method focusOption
   * @private
   */

  focusOption: function(option, options) {
    options = options || {};
    var focusedOption = this.get('focusedOption');
    if (focusedOption) {
      focusedOption.blur();
    }
    this.set('focusedOption', option);
    if (options.focus !== false) {
      option.focus(options);
    }
  },

  /**
   * Selects the focused item if there is one.
   *
   * @method maybeSelectFocusedOption
   * @private
   */

  maybeSelectFocusedOption: function() {
    var focused = this.get('focusedOption');
    if (focused) this.selectOption(focused);
  },

  /**
   * Selects the autocompleted option if there is one.
   *
   * @method maybeSelectAutocompletedOption
   * @private
   */

  maybeSelectAutocompletedOption: function() {
    var option = this.get('autocompletedOption');
    if (option) {
      this.selectOption(option, {focus: false, focusOption: false});
      this.set('autocompletedOption', null);
    }
  },

  /**
   * Selects the focused or autocompleted option when the user hits enter on
   * the keyboard. This is nice because then it selects all the text so they
   * can start typing again without having to delete everything first.
   *
   * @method maybeSelectOnEnter
   * @private
   */

  maybeSelectOnEnter: function(event) {
    event.preventDefault();
    this.maybeSelectFocusedOption();
    this.maybeSelectAutocompletedOption();
    this.get('input').select();
  },

  /**
   * Finds the input element
   * TODO: do the typical register call from the input component instead
   *
   * @method registerInput
   * @private
   */

  registerInput: function() {
    this.set('input', this.$('input')[0]);
  }.on('didInsertElement'),

  /**
   * @method registerList
   * @private
   */

  registerList: function(list) {
    this.set('list', list);
  },

  /**
   * @method toggleVisibility
   * @public
   */

  toggleVisibility: function() {
    if (this.get('isOpen')) {
      this.close();
    } else {
      this.open();
    }
  },

  /**
   * Closes the list when focus moves away from the component. If there is a
   * valid autocomplete happening, it selects it (it would be weird
   * to have a valid label inside the text field but not have the autocomplete
   * `value` match).
   *
   * @method closeOnFocusOut
   * @private
   */

  closeOnFocusOut: function() {
    // later for document.activeElement to be correct
    Ember.run.later(this, function() {
      // TODO: maybe handle focusOut of the elements we know about instead of an
      // overarching check here?
      if (!this.get('element').contains(document.activeElement)) {
        this.maybeSelectAutocompletedOption();
      }
    }, 0);
  }.on('focusOut'),

  /**
   * Observes the `value` property and selects the option with a matching value
   * if there is one.
   *
   * @method bindValue
   * @private
   */

  bindValue: function() {
    var selected = this.get('selected');
    var value = this.get('value');
    // don't do the work if we know nothing is going to match.
    if (!selected && (value == null || value === '')) {
      return;
    }
    // if it already matches, don't do any work
    if (selected && value === selected.get('value')) {
      return;
    }
    var option = this.optionsMap[value];
    if (option) {
      // TODO: learn why we need the run loop here for the tests
      Ember.run(this, 'selectOption', option);
    }
  }.observes('value')

});

<commit_msg>[fixed] closing on focusOut with non-matching text<commit_after>import Ember from 'ember';

export default Ember.Component.extend({

  tagName: 'ic-autocomplete',

  attributeBindings: [
    'is-open'
  ],

  inputValue: '',

  /**
   * Two-way bound property representing the current value.
   *
   * @property value
   * @public
   */

  value: null,

  /**
   * Becomes the placeholder of the input
   *
   * @property placeholder
   * @public
   */

  placeholder: null,

  /**
   * Attribute binding to allow styling of the component when it is open or
   * not. Style with `ic-autocomplete[is-open] {}`
   *
   * @property is-open
   * @readonly
   */

  'is-open': function() {
    return this.get('isOpen') ? 'true' : null;
  }.property('isOpen'),

  /**
   * Determines whether or not the list is open.
   *
   * @property isOpen
   * @readonly
   */

  isOpen: false,

  /**
   * Adds the cache to track all options in the list to facilitate option
   * focus, blur, and navigation, etc.
   *
   * @method createOptions
   * @private
   */

  createOptions: function() {
    this.set('options', Ember.ArrayProxy.create({content: []}));
    this.optionsMap = {};
  }.on('init'),

  /**
   * Opens the list.
   *
   * @method open
   * @public
   */

  open: function() {
    if (this.get('isOpen')) return;
    this.set('isOpen', true);
  },

  /**
   * Occasionally we want to focus the input without triggering the list to open,
   * when this is true, the focus handler will be ignored.
   *
   * @property ignoreInputFocus
   * @private
   */

  ignoreInputFocus: false,

  /**
   * Closes the popover.
   *
   * @method close
   * @public
   */

  close: function() {
    if (!this.get('isOpen')) return;
    this.set('isOpen', false);
    var focusedOption = this.get('focusedOption');
    if (focusedOption) {
      focusedOption.blur();
      // so that keyboard starts at the right place on "down" or "up"
      this.set('focusedOption', this.get('selected'));
    }
    // if the active element is an element in the component, then we want to
    // focus the input and prevent the focus from opening it again
    if (this.get('element').contains(document.activeElement)) {
      this.set('ignoreInputFocus', true);
      Ember.run.later(this, 'set', 'ignoreInputFocus', false, 0);
      this.get('input').focus();
    }
  },

  /**
   * Selects an option.
   *
   * @method selectOption
   * @param option AutocompleteOptionComponent
   * @param options Object - TODO: kill these options, should instead make
   * `selectOption` do less, and have the call sites do the other operations
   * (like focusing the input, etc).
   *   @property close Boolean - Defaults true; if false, the menu will not
   *   close after the option is selected.
   *   @property focus Boolean - Defaults true; if false, the input will not
   *   get focus.
   *   @property focusOption Boolean - Defaults true; if false, the option will
   *   not get focus.
   * @private
   */

  selectOption: function(option, options) {
    options = options || {};
    var selected = this.get('selected');
    if (selected) selected.deselect();
    this.set('selected', option);
    this.set('inputValue', this.get('selected.label'));
    option.select();
    this.focusOption(option, {focusElement: options.focusOption});
    if (options.focus !== false) {
      this.get('input').focus();
    }
    if (options.close !== false) {
      this.close();
    }
    this.sendAction('on-select', this, option);
  },

  /**
   * Handle case where apps reset the list on select, you get new options
   * all registering and therefore an infinite loop of selecting the option
   * whose value matches the current value
   *
   * TODO: DRY up with selectOption (maybe?), or maybe there's a better way
   * to prevent this when the option registers? The fact that we're doing the
   * same operation minus some side-effects is unsettling.
   *
   * @method selectOptionWithoutPotentialRecursion
   * @private
   */

  selectOptionWithoutPotentialRecursion: function(option) {
    this.set('selected', option);
    option.select();
    this.focusOption(option, {focus: false});
  },

  /**
   * @method setValueFromSelected
   * @private
   */

  setValueFromSelected: function() {
    this.set('value', this.get('selected.value'));
  }.observes('selected'),

  /**
   * Adds an option to the options cache, selects it if the value matches.
   *
   * @method registerOption
   * @private
   */

  registerOption: function(option) {
    this.get('options').pushObject(option);
    this.optionsMap[option.get('value')] = option;
    if (this.get('value') === option.get('value')) {
      if (this.get('selected')) {
        // When a new option shows up with matching attributes as the selected
        // option, select this new one instead but don't explode...  TODO: use
        // `ignoreInputFocus` or probably better, when a the selection option
        // is unregistered, clear out `selected` and then we dont' have to do
        // this dance.
        this.selectOptionWithoutPotentialRecursion(option);
      } else {
        // When an option shows up with a matching value, select it but don't
        // focus because we don't want to steal focus from the rest of the
        // page, the user isn't actually interacting with the component.
        this.selectOption(option, {focus: false});
      }
    }

  },

  /**
   * Removes an option from the options cache.
   *
   * TODO: should probably get rid of 'selected' if its selected.
   *
   * @method removeOption
   * @private
   */

  removeOption: function(optionComponent) {
    if (this.get('focusedOption') === optionComponent) {
      this.set('focusedOption', null);
    }
    this.get('options').removeObject(optionComponent);
  },

  /**
   * We don't want autocomplete to happen on backspace (feels super weird),
   * so set the `isBackspacing` property so autocomplete won't do its thing.
   *
   * @method startBackspacing
   * @private
   */

  startBackspacing: function() {
    // prevents autocomplete from not happening (yes, double negative) on
    // backspacing an empty input
    // TODO: remember what the side-effect is ...
    if (this.get('inputValue') === '') {
      return;
    }
    this.set('isBackspacing', true);
  },

  /**
   * Flow control for handling keydown.
   *
   * @property keydownMap
   * @private
   */

  keydownMap: {
    27/*esc*/:   'close',
    32/*space*/: 'maybeSelectFocusedOption',
    13/*enter*/: 'maybeSelectOnEnter',
    40/*down*/:  'focusNext',
    38/*up*/:    'focusPrevious',
    8/*backspace*/: 'startBackspacing'
  },

  /**
   * Handles keyboard interactions from all elements in the component.
   *
   * @method handleKeydown
   * @private
   */

  handleKeydown: function(event) {
    var map = this.get('keydownMap');
    var method = map[event.keyCode];
    if (this[method]) {
      return this[method](event);
    }
    var input = this.get('input');
    // After this we focus the input, but if they are using shift, we don't
    // want to actually do it (they are probably shift+tabbing away). This is a
    // blacklist of one, which makes me really nervous. We want to allow any
    // valid input character, but that's a huge whitelist, or maybe use the
    // run loop and wait for focus to settle on the new element and then decide
    // what to do.
    if (event.shiftKey) {
      return;
    }
    if (document.activeElement !== input) {
      input.focus();
      // if its not backspace, then we want to select the input, since its
      // keyDown, then on keyUp the contents will be replaced, but with
      // backspace, we dont' want to do that.
      if (event.keyCode !== 8/*backspace*/) {
        input.select();
      }
    }
  }.on('keyDown'),

  /**
   * When the user is typing we autocomplete with the label of the first option
   * in the list. The autocompleted text gets selected so the component doesn't
   * get in the way of typing.
   *
   * @method autocompleteText
   * @private
   */

  autocompleteText: function() {
    this.set('autocompletedOption', null);
    if (!this.get('isOpen') || !this.get('options.length')) {
      return;
    }
    if (this.get('isBackspacing')) {
      this.set('isBackspacing', false);
      return;
    }
    var first = this.get('options').objectAt(0);
    if (first == this.get('autocompletedOption')) {
      return;
    }
    var label = first.get('label');
    var input = this.get('inputValue');
    if (input === '') {
      return;
    }
    var fragment = label.substring(input.length);
    // since we are setting the input's value, we don't want the observers
    // doing their thing
    this.set('ignoreInputValue', true);
    // tests need this, if the body is the active element when the test starts
    // then it fails, if anything else has focus its fine :\
    this.set('ignoreInputFocus', true);
    Ember.run.later(this, 'set', 'ignoreInputFocus', false, 0);
    var el = this.get('input');
    el.value = label;
    el.setSelectionRange(input.length, label.length);
    this.set('autocompletedOption', first);
  },

  /**
   * Observes the input's value and does a bunch of stuff... including sending
   * the `on-input` action.
   *
   * @method onInput
   * @private
   */

  onInput: function() {
    if (!this.get('isOpen')) {
      this.open();
    }
    // autocomplete set it, so just bail
    if (this.get('ignoreInputValue')) {
      this.set('ignoreInputValue', false);
      return;
    }
    // we don't match anymore, clear selected, TODO: clear focused too?
    if (this.get('selected.label') !== this.get('inputValue')) {
      this.set('selected', null);
    }
    if (this.get('selected')) {
      // TODO: WHY IS THIS HERE!?
      return;
    }
    this.sendAction('on-input', this, this.get('inputValue'));
    // TODO: later because ???
    Ember.run.scheduleOnce('afterRender', this, 'autocompleteText');
  }.observes('inputValue'),

  /**
   * Focuses the next option in the list.
   *
   * @method focusNext
   * @private
   */

  focusNext: function(event) {
    event.preventDefault();
    var index = 0;
    var focusedOption = this.get('focusedOption');
    if (focusedOption) {
      index = this.get('options').indexOf(focusedOption);
      if (this.get('isOpen')) {
        index = index + 1;
      }
    }
    this.focusOptionAtIndex(index);
  },

  /**
   * Focuses the previous option in the popover.
   *
   * @method focusPrevious
   * @private
   */

  focusPrevious: function(event) {
    event.preventDefault();
    var focusedOption = this.get('focusedOption');
    if (!focusedOption) return;
    var index = this.get('options').indexOf(focusedOption);
    if (this.get('isOpen')) {
      index = index - 1;
    }
    this.focusOptionAtIndex(index);
  },

  /**
   * Focuses an option given an index in the options cache.
   *
   * @method focusOptionAtIndex
   * @private
   */

  focusOptionAtIndex: function(index) {
    var options = this.get('options');
    // loop it when at the ends
    if (index === -1) {
      index = options.get('length') - 1;
    } else if (index === options.get('length')) {
      index = 0;
    }
    var option = this.get('options').objectAt(index);
    if (!option) return;
    this.focusOption(option);
  },

  /**
   * Sets the option as the `focusedOption`
   *
   * @method focusOption
   * @private
   */

  focusOption: function(option, options) {
    options = options || {};
    var focusedOption = this.get('focusedOption');
    if (focusedOption) {
      focusedOption.blur();
    }
    this.set('focusedOption', option);
    if (options.focus !== false) {
      option.focus(options);
    }
  },

  /**
   * Selects the focused item if there is one.
   *
   * @method maybeSelectFocusedOption
   * @private
   */

  maybeSelectFocusedOption: function() {
    var focused = this.get('focusedOption');
    if (focused) this.selectOption(focused);
  },

  /**
   * Selects the autocompleted option if there is one.
   *
   * @method maybeSelectAutocompletedOption
   * @private
   */

  maybeSelectAutocompletedOption: function() {
    var option = this.get('autocompletedOption');
    if (option) {
      this.selectOption(option, {focus: false, focusOption: false});
      this.set('autocompletedOption', null);
    }
  },

  /**
   * Selects the focused or autocompleted option when the user hits enter on
   * the keyboard. This is nice because then it selects all the text so they
   * can start typing again without having to delete everything first.
   *
   * @method maybeSelectOnEnter
   * @private
   */

  maybeSelectOnEnter: function(event) {
    event.preventDefault();
    this.maybeSelectFocusedOption();
    this.maybeSelectAutocompletedOption();
    this.get('input').select();
  },

  /**
   * Finds the input element
   * TODO: do the typical register call from the input component instead
   *
   * @method registerInput
   * @private
   */

  registerInput: function() {
    this.set('input', this.$('input')[0]);
  }.on('didInsertElement'),

  /**
   * @method registerList
   * @private
   */

  registerList: function(list) {
    this.set('list', list);
  },

  /**
   * @method toggleVisibility
   * @public
   */

  toggleVisibility: function() {
    if (this.get('isOpen')) {
      this.close();
    } else {
      this.open();
    }
  },

  /**
   * Closes the list when focus moves away from the component. If there is a
   * valid autocomplete happening, it selects it (it would be weird
   * to have a valid label inside the text field but not have the autocomplete
   * `value` match).
   *
   * @method closeOnFocusOut
   * @private
   */

  closeOnFocusOut: function() {
    // later for document.activeElement to be correct
    Ember.run.later(this, function() {
      // TODO: maybe handle focusOut of the elements we know about instead of an
      // overarching check here?
      if (!this.get('element').contains(document.activeElement)) {
        this.maybeSelectAutocompletedOption();
        this.close();
      }
    }, 0);
  }.on('focusOut'),

  /**
   * Observes the `value` property and selects the option with a matching value
   * if there is one.
   *
   * @method bindValue
   * @private
   */

  bindValue: function() {
    var selected = this.get('selected');
    var value = this.get('value');
    // don't do the work if we know nothing is going to match.
    if (!selected && (value == null || value === '')) {
      return;
    }
    // if it already matches, don't do any work
    if (selected && value === selected.get('value')) {
      return;
    }
    var option = this.optionsMap[value];
    if (option) {
      // TODO: learn why we need the run loop here for the tests
      Ember.run(this, 'selectOption', option);
    }
  }.observes('value')

});

<|endoftext|>


<commit_before>(function(module) {
  var bookView = {};

  var render = function(item, templateId) {
    var template = Handlebars.compile($(templateId).text());
    return template(item);
  };

  bookView.showResults = function(data) {
    $('#results').empty();
    $('#results').siblings().fadeOut();

    data.forEach(function(item){
      $('#results').append(render(item,'#thumbnail-template'));
    });
    $('#results').fadeIn();
  };

  bookView.showBookDetails = function(book){
    $('#book-details').append(render(book, '#detail-template'));

    book.grRecommendations.forEach(function(recommendedBook){
      $('#book-details').append(render(recommendedBook, '#rec-template'));
    });

    $('.book-detail-backdrop').fadeIn().children().fadeIn();
  };

  bookView.handleSearchInput = function() {
    $('#form-input').on('change', bookModel.createNewInput);
    $('#form-input').on('submit', function(e) {
      e.preventDefault();
      $('#form-input').on('submit', page('/search/' + bookModel.createEndpoint()));
    });
  };

  bookView.handleClickBook = function() {
    $('#results').on('click', '.book', function(){
      bookModel.requestGoodReadsData(bookModel.all[$(this).index()]);
    });

    $('.book-details').on('scroll', function() {
      var distance = $(this).scrollTop();
      $('.close-details').css('top', distance);
    });

    $('.close-details').on('click', function() {
      $('.close-details').siblings().remove();
      $('.book-detail-backdrop').fadeOut().children().fadeOut();
    });
  };

  bookView.initIndexPage = function() {
    $('main').children().hide();
    $('.form').fadeIn();
    bookView.handleSearchInput();
  };

  bookView.initTeamPage = function(){
    $('main').children().hide();
    $('.container-fluid').fadeIn();
  };

  module.bookView = bookView;
})(window);
<commit_msg>recommendations fix<commit_after>(function(module) {
  var bookView = {};

  var render = function(item, templateId) {
    var template = Handlebars.compile($(templateId).text());
    return template(item);
  };

  bookView.showResults = function(data) {
    $('#results').empty();
    $('#results').siblings().fadeOut();

    data.forEach(function(item){
      $('#results').append(render(item,'#thumbnail-template'));
    });
    $('#results').fadeIn();
  };

  bookView.showBookDetails = function(book){
    $('.close-details').siblings().remove();
    $('#book-details').append(render(book, '#detail-template'));

    book.grRecommendations.forEach(function(recommendedBook){
      $('#book-details').append(render(recommendedBook, '#rec-template'));
    });

    $('.book-detail-backdrop').fadeIn().children().fadeIn();
  };

  bookView.handleSearchInput = function() {
    $('#form-input').on('change', bookModel.createNewInput);
    $('#form-input').on('submit', function(e) {
      e.preventDefault();
      $('#form-input').on('submit', page('/search/' + bookModel.createEndpoint()));
    });
  };

  bookView.handleClickBook = function() {
    $('#results').on('click', '.book', function(){
      bookModel.requestGoodReadsData(bookModel.all[$(this).index()]);
    });

    $('.book-details').on('scroll', function() {
      var distance = $(this).scrollTop();
      $('.close-details').css('top', distance);
    });

    $('.close-details').on('click', function() {
      $('.close-details').siblings().remove();
      $('.book-detail-backdrop').fadeOut().children().fadeOut();
    });
  };

  bookView.initIndexPage = function() {
    $('main').children().hide();
    $('.form').fadeIn();
    bookView.handleSearchInput();
  };

  bookView.initTeamPage = function(){
    $('main').children().hide();
    $('.container-fluid').fadeIn();
  };

  module.bookView = bookView;
})(window);
<|endoftext|>


<commit_before>/**
 * Created by Dana Zhang on 3/31/2014.
 */

define(['plugin/PluginConfig',
    'plugin/PluginBase',
    'plugin/PluginResult',
    'plugin/PetriNetExporter/PetriNetExporter/XMLWriter'], function (PluginConfig, PluginBase, PluginResult, XMLWriter) {
    'use strict';

    // TODO: to modify the base dir path in config.json to allow dependencies from other dirs

    var PetriNetExporterPlugin = function () {
        PluginBase.call(this);
    };

    PetriNetExporterPlugin.prototype = Object.create(PluginBase.prototype);

    PetriNetExporterPlugin.prototype.getName = function () {
        return 'PetriNetExporter';
    };

    PetriNetExporterPlugin.prototype.main = function (callback) {
        var self = this,
            core = self.core,
            selectedNode = self.activeNode;

        var pluginResult = new PluginResult();

        if (!selectedNode) {
            callback('selectedNode is not defined', pluginResult);
        }

        // TODO: this is not the right way to do it..., but a way at least
        this.objectToVisit = 0; // number of objects that have to be visited
        this.visitedObjects = 0; // number of already visited

        this.objectToVisit += 1; // we need to visit the selected node

        core.loadChildren(selectedNode, function(err, childNodes) {
            self.visitObject(err, childNodes, core, callback);
        });

        // TODO: extend XML writer to accept n numbers of attributes

        var xw = new XMLWriter;
        xw.startDocument();
        xw.startElement('root').writeAttribute('foo', 'value');

        xw.writeElement('bar', 'barbar').writeAttribute('foobar', 'value');
        xw.text('Some content');
        xw.endDocument();

        console.log(xw.toString());
    };

    PetriNetExporterPlugin.prototype.visitObject = function (err, childNodes, core, callback) {
        var self = this;

        this.objectToVisit += childNodes.length; // all child objects have to be visited

        var i;
        for (i = 0; i < childNodes.length; ++i) {

            var child = childNodes[i];

            // get its base META Type
            var baseClass = core.getBase(child);
            if (baseClass) {
                var metaType = core.getAttribute(baseClass, 'name');
            }

            if (metaType === 'Place') {

            } else if (metaType === 'Transition') {

            } else if (metaType === 'Arc') {

            }

            var name = core.getAttribute(child, 'name'),
                capacity = core.getAttribute(child, 'Capacity'),
                marking = core.getAttribute(child, 'InitialMarking'),
                xPos = child.data.reg.position.x,
                yPos = child.data.reg.position.y;

            //    HEIGHT = isTypePlace ? 30 : 20; can we use META Aspect

            core.loadChildren(childNodes[i], function(err, childNodes) {
                self.visitObject(err, childNodes, core, callback);
            });
        }

        this.visitedObjects += 1; // another object was just visited

        if (this.objectToVisit === this.visitedObjects) {
            this.fs.addFile('generatedFile.json', JSON.stringify({'name':'some content'}));
            this.fs.addFile('generatedFile.html', 'Hello world from an xml');
            this.fs.saveArtifact();

            // all objects have been visited
            var pluginResult = new PluginResult();
            pluginResult.success = true;
            if (callback) {
                callback(null, pluginResult);
            }
        } else {
            // some objects still need to be visited
            this.logger.info('Visiting progress: ' + this.visitedObjects + '/' + this.objectToVisit);
        }
    };

    return PetriNetExporterPlugin;
});<commit_msg>#2 Exposed xmlwriter globally<commit_after>/**
 * Created by Dana Zhang on 3/31/2014.
 */

define(['plugin/PluginConfig',
    'plugin/PluginBase',
    'plugin/PluginResult',
    'plugin/PetriNetExporter/PetriNetExporter/XMLWriter'], function (PluginConfig, PluginBase, PluginResult, XMLWriter) {
    'use strict';

    // TODO: to modify the base dir path in config.json to allow dependencies from other dirs

    var PetriNetExporterPlugin = function () {
        PluginBase.call(this);
    };

    PetriNetExporterPlugin.prototype = Object.create(PluginBase.prototype);

    PetriNetExporterPlugin.prototype.getName = function () {
        return 'PetriNetExporter';
    };

    PetriNetExporterPlugin.prototype.main = function (callback) {
        var self = this,
            core = self.core,
            selectedNode = self.activeNode;

        var pluginResult = new PluginResult();

        if (!selectedNode) {
            callback('selectedNode is not defined', pluginResult);
        }

        // TODO: this is not the right way to do it..., but a way at least
        this.objectToVisit = 0; // number of objects that have to be visited
        this.visitedObjects = 0; // number of already visited

        this.objectToVisit += 1; // we need to visit the selected node

        this.diagram = 0;

        // only when node is diagram folder then traverse
        core.loadChildren(selectedNode, function(err, childNodes) {
            self.visitObject(err, childNodes, core, callback);
        });

        // TODO: extend XML writer to accept n attributes

        this.xml = new XMLWriter;

        this.xml.startDocument();
        this.xml.startElement('root').writeAttribute('foo', 'value');

        this.xml.writeElement('bar', 'barbar').writeAttribute('foobar', 'value');
        this.xml.text('Some content');
        this.xml.endDocument();

        console.log(this.xml.toString());
    };

    PetriNetExporterPlugin.prototype.visitObject = function (err, childNodes, core, callback) {
        var self = this;

        this.objectToVisit += childNodes.length; // all child objects have to be visited

        var i;
        for (i = 0; i < childNodes.length; ++i) {

            var child = childNodes[i];

            // get its base META Type
            var baseClass = core.getBase(child);
            if (baseClass) {
                var metaType = core.getAttribute(baseClass, 'name');
            }

            if (metaType === 'Place') {

            } else if (metaType === 'Transition') {

            } else if (metaType === 'Arc') {

            } else if (metaType === 'PetriNetDiagram') {

                this.diagram += 1;
                this.xml.startDocument('1.0', 'utf-8');

            }

            console.log(core.getAttribute(child, 'name'));
            console.log(core.getAttribute(child.parent, 'name'));

            var name = core.getAttribute(child, 'name'),
                capacity = core.getAttribute(child, 'Capacity'),
                marking = core.getAttribute(child, 'InitialMarking'),
                xPos = child.data.reg.position.x,
                yPos = child.data.reg.position.y;

            //    HEIGHT = isTypePlace ? 30 : 20; can we use META Aspect

            core.loadChildren(childNodes[i], function(err, childNodes) {
                self.visitObject(err, childNodes, core, callback);
            });
        }

        this.visitedObjects += 1; // another object was just visited

        if (this.objectToVisit === this.visitedObjects) {
            this.fs.addFile('generatedFile.json', JSON.stringify({'name':'some content'}));
            this.fs.addFile('generatedFile.html', 'Hello world from an xml');
            this.fs.saveArtifact();

            // all objects have been visited
            var pluginResult = new PluginResult();
            pluginResult.success = true;
            if (callback) {
                callback(null, pluginResult);
            }
        }

//        else {
//            some objects still need to be visited
//            this.logger.info('Visiting progress: ' + this.visitedObjects + '/' + this.objectToVisit);
//        }
    };

    return PetriNetExporterPlugin;
});<|endoftext|>


<commit_before>/*!
 * ${copyright}
 */

// Provides ControllerMetadata
sap.ui.define([
	'sap/ui/base/Metadata',
	'sap/base/util/merge',
	'sap/ui/core/mvc/OverrideExecution',
	"sap/base/Log"
],
	function(Metadata, merge, OverrideExecution, Log) {
	"use strict";

	/**
	 * @alias sap.ui.core.mvc.ControllerMetadata
	 * @extends sap.ui.base.Metadata
	 * @private
	 */
	var ControllerMetadata = function(sClassName, oClassInfo) {
		// call super constructor
		Metadata.apply(this, arguments);

		// propagate static functions to subclasses of ControllerExtension
		if (this.isA("sap.ui.core.mvc.ControllerExtension") && this.getParent().getClass().override ) {
			this.getClass().override = this.getParent().getClass().override;
		}
	};

	// chain the prototypes
	ControllerMetadata.prototype = Object.create(Metadata.prototype);
	ControllerMetadata.prototype.constructor = ControllerMetadata;

	ControllerMetadata.prototype.applySettings = function(oClassInfo) {
		// property 'override' needs to be handled separately and must not be attached to the prototype
		if (oClassInfo.override) {
			this._override = oClassInfo.override;
			delete oClassInfo.override;
		}

		Metadata.prototype.applySettings.call(this, oClassInfo);

		var oStaticInfo = oClassInfo.metadata;

		this._defaultLifecycleMethodMetadata = {
			"onInit":               {"public": true, "final": false, "overrideExecution": OverrideExecution.After},
			"onExit":               {"public": true, "final": false, "overrideExecution": OverrideExecution.Before},
			"onBeforeRendering":    {"public": true, "final": false, "overrideExecution": OverrideExecution.Before},
			"onAfterRendering":     {"public": true, "final": false, "overrideExecution": OverrideExecution.After}
		};

		var bIsExtension = this.isA("sap.ui.core.mvc.ControllerExtension");

		var rPrivateCheck = /^_/;

		var bExtendsController = this._oParent.isA("sap.ui.core.mvc.Controller");

		var bDefinesMethods = oClassInfo.metadata && oClassInfo.metadata.methods ? true : false;

		if (!bIsExtension) {
			/*
			* If methods are defined in metadata, only methods prefixed with '_' get private.
			* If not, we stay compatible and every method prefixed with '-' or 'on' gets private.
			*/
			if (bExtendsController && !bDefinesMethods) {
			   rPrivateCheck = /^_|^on|^init$|^exit$/;
			}

			/*
			* extend method metadata: make lifecycle hooks public
			*/
			if (bExtendsController && bDefinesMethods) {
			    merge(oStaticInfo.methods, this._defaultLifecycleMethodMetadata);
			}
		}

		//check public methods
		//in legacy scenarion the public methods defined in metadata must not be deleted
		if (bIsExtension || bDefinesMethods) {
			this._aPublicMethods = [];
		}
		this._mMethods = oStaticInfo.methods || {};
		for ( var n in oClassInfo ) {
			if ( n !== "metadata" && n !== "constructor") {
				if (!n.match(rPrivateCheck)) {
					//final check
					if (bExtendsController && this._oParent && this._oParent.isMethodFinal(n)) {
						Log.error("Method: '" + n + "' of controller '" + this._oParent.getName() + "' is final and cannot be overridden by controller '" + this.getName() + "'");
						delete this._oClass.prototype[n];
					}
					// default metadata for methods
					if (!(n in this._mMethods) && typeof oClassInfo[n] === 'function') {
						// do not provide metadata for extension members
						if (!(oClassInfo[n].getMetadata && oClassInfo[n].getMetadata().isA("sap.ui.core.mvc.ControllerExtension"))) {
							this._mMethods[n] = {"public": true, "final": false};
						}
					}
				}
			}
		}
		for (var m in this._mMethods) {
			if (this.isMethodPublic(m)) {
			    this._aPublicMethods.push(m);
			}
		}
	};

	/**
	 * Called after new settings have been applied.
	 *
	 * Typically, this method is used to do some cleanup (e.g. uniqueness)
	 * or to calculate an optimized version of some data.
	 * @private
	 * @since 1.3.1
	 */
	ControllerMetadata.prototype.afterApplySettings = function() {
		Metadata.prototype.afterApplySettings.call(this);
		var bIsExtension = this.isA("sap.ui.core.mvc.ControllerExtension");
		if (this._oParent) {
			var mParentMethods = this._oParent._mMethods ? this._oParent._mMethods : {};
			//allow change of visibility but not the other attributes
			for (var sMethod in mParentMethods) {
			if (this._mMethods[sMethod] && !bIsExtension) {
			var bPublic = this._mMethods[sMethod].public;
			//copy parent method definition as final/overrideExecution should not be overridden
			this._mMethods[sMethod] = merge({}, mParentMethods[sMethod]);
			if (bPublic !== undefined) {
			this._mMethods[sMethod].public = bPublic;
			}
			if (!this.isMethodPublic(sMethod) && this._mMethods[sMethod].public !== mParentMethods[sMethod].public) {
			//if visibility changed to private delete from public methods
			this._aAllPublicMethods.splice(this._aAllPublicMethods.indexOf(sMethod), 1);
			}
			} else {
			this._mMethods[sMethod] = mParentMethods[sMethod];
			}
			}
		}

		//flag each extension as final (but not the class ControllerExtension itself)
		if (this._oParent && this._oParent.isA("sap.ui.core.mvc.ControllerExtension")) {
		   this._bFinal = true;
		}
	};

	/**
	 * Returns the Namespace for a ControllerExtension. The Namespace is
	 * extracted from the ControllerExtensions name:
	 *

	 *   name: my.name.space.Extension
	 *   namespace = my.name.space
	 *

	 * @return {string} The Namespace
	 * @public
	 */
	ControllerMetadata.prototype.getNamespace = function() {
		var bIsAnonymous = this._sClassName.indexOf("anonymousExtension~") == 0;
		var sNamespace = bIsAnonymous ? this._oParent._sClassName : this._sClassName;
		return sNamespace.substr(0,sNamespace.lastIndexOf("."));
	};

	/**
	 * Check if method is flagged as final.
	 *
	 * @param {string} sMethod Name of the method
	 * @return {boolean} Whether the method is flagged as final or not
	 */
	ControllerMetadata.prototype.isMethodFinal = function(sMethod) {
		var oMethodMetadata = this._mMethods[sMethod];
		return oMethodMetadata && oMethodMetadata.final;
	};

	/**
	 * Check if method is flagged as public.
	 *
	 * @param {string} sMethod Name of the method
	 * @return {boolean} Whether the method is flagged as public or not
	 */
	ControllerMetadata.prototype.isMethodPublic = function(sMethod) {
		var oMethodMetadata = this._mMethods[sMethod];
		return oMethodMetadata && oMethodMetadata.public;
	};

	/**
	 * Get all defined methods and their metadata.
	 *
	 * @return {Object<string,object>} A map containing all methods (key) and their metadata
	 */
	ControllerMetadata.prototype.getAllMethods = function() {
		return this._mMethods;
	};

	/**
	 * Returns the override execution strategy for the given method.
	 *
	 * @param {string} sMethod Name of the method
	 * @return {sap.ui.core.mvc.OverrideExecution} The override execution strategy
	 */
	ControllerMetadata.prototype.getOverrideExecution = function(sMethod) {
		var oMethodMetadata = this._mMethods[sMethod];
		var sOverrideExecution = OverrideExecution.Instead;
		if (oMethodMetadata) {
			sOverrideExecution = oMethodMetadata.overrideExecution;
		}
		return sOverrideExecution;
	};

	 /**
	 * Returns the override definition of this extension.
	 *
	 * @return {object} The overrides
	 * @private
	 */
	ControllerMetadata.prototype.getOverrides = function() {
		return this._override;
	};

	 /**
	 * Returns the 'static' override definition registered by the override function for this extension
	 *
	 * @return {object} The static overrides
	 * @private
	 */
	ControllerMetadata.prototype.getStaticOverrides = function() {
		return this._staticOverride;
	};

	/**
	 * Checks whether override definitions exists.
	 *
	 * @return {boolean} Whether override definitions exists
	 * @private
	 */
	ControllerMetadata.prototype.hasOverrides = function() {
		return !!this._override || !!this._staticOverride;
	};

	/**
	 * Get configuration for extending lifecycle methods.
	 *
	 * @return {Object<string,object>} A map containing the lifecycle configuration metadata
	 * @private
	 */
	ControllerMetadata.prototype.getLifecycleConfiguration = function() {
		return this._defaultLifecycleMethodMetadata;
	};

	return ControllerMetadata;
});<commit_msg>[INTERNAL] sap.ui.core.mvc.ControllerMetadata: improve JSDoc<commit_after>/*!
 * ${copyright}
 */

// Provides ControllerMetadata
sap.ui.define([
	'sap/ui/base/Metadata',
	'sap/base/util/merge',
	'sap/ui/core/mvc/OverrideExecution',
	"sap/base/Log"
],
	function(Metadata, merge, OverrideExecution, Log) {
	"use strict";

	/**
	 * The controller metadata
	 *
	 * @param {string} sClassName Fully qualified name of the described class
	 * @param {object} oClassInfo Info to construct the class and its metadata from
	 *
	 * @alias sap.ui.core.mvc.ControllerMetadata
	 * @extends sap.ui.base.Metadata
	 * @private
	 */
	var ControllerMetadata = function(sClassName, oClassInfo) {
		// call super constructor
		Metadata.apply(this, arguments);

		// propagate static functions to subclasses of ControllerExtension
		if (this.isA("sap.ui.core.mvc.ControllerExtension") && this.getParent().getClass().override ) {
			this.getClass().override = this.getParent().getClass().override;
		}
	};

	// chain the prototypes
	ControllerMetadata.prototype = Object.create(Metadata.prototype);
	ControllerMetadata.prototype.constructor = ControllerMetadata;

	ControllerMetadata.prototype.applySettings = function(oClassInfo) {
		// property 'override' needs to be handled separately and must not be attached to the prototype
		if (oClassInfo.override) {
			this._override = oClassInfo.override;
			delete oClassInfo.override;
		}

		Metadata.prototype.applySettings.call(this, oClassInfo);

		var oStaticInfo = oClassInfo.metadata;

		this._defaultLifecycleMethodMetadata = {
			"onInit":               {"public": true, "final": false, "overrideExecution": OverrideExecution.After},
			"onExit":               {"public": true, "final": false, "overrideExecution": OverrideExecution.Before},
			"onBeforeRendering":    {"public": true, "final": false, "overrideExecution": OverrideExecution.Before},
			"onAfterRendering":     {"public": true, "final": false, "overrideExecution": OverrideExecution.After}
		};

		var bIsExtension = this.isA("sap.ui.core.mvc.ControllerExtension");

		var rPrivateCheck = /^_/;

		var bExtendsController = this._oParent.isA("sap.ui.core.mvc.Controller");

		var bDefinesMethods = oClassInfo.metadata && oClassInfo.metadata.methods ? true : false;

		if (!bIsExtension) {
			/*
			* If methods are defined in metadata, only methods prefixed with '_' get private.
			* If not, we stay compatible and every method prefixed with '-' or 'on' gets private.
			*/
			if (bExtendsController && !bDefinesMethods) {
			   rPrivateCheck = /^_|^on|^init$|^exit$/;
			}

			/*
			* extend method metadata: make lifecycle hooks public
			*/
			if (bExtendsController && bDefinesMethods) {
			    merge(oStaticInfo.methods, this._defaultLifecycleMethodMetadata);
			}
		}

		//check public methods
		//in legacy scenarion the public methods defined in metadata must not be deleted
		if (bIsExtension || bDefinesMethods) {
			this._aPublicMethods = [];
		}
		this._mMethods = oStaticInfo.methods || {};
		for ( var n in oClassInfo ) {
			if ( n !== "metadata" && n !== "constructor") {
				if (!n.match(rPrivateCheck)) {
					//final check
					if (bExtendsController && this._oParent && this._oParent.isMethodFinal(n)) {
						Log.error("Method: '" + n + "' of controller '" + this._oParent.getName() + "' is final and cannot be overridden by controller '" + this.getName() + "'");
						delete this._oClass.prototype[n];
					}
					// default metadata for methods
					if (!(n in this._mMethods) && typeof oClassInfo[n] === 'function') {
						// do not provide metadata for extension members
						if (!(oClassInfo[n].getMetadata && oClassInfo[n].getMetadata().isA("sap.ui.core.mvc.ControllerExtension"))) {
							this._mMethods[n] = {"public": true, "final": false};
						}
					}
				}
			}
		}
		for (var m in this._mMethods) {
			if (this.isMethodPublic(m)) {
			    this._aPublicMethods.push(m);
			}
		}
	};

	/**
	 * Called after new settings have been applied.
	 *
	 * Typically, this method is used to do some cleanup (e.g. uniqueness)
	 * or to calculate an optimized version of some data.
	 * @private
	 * @since 1.3.1
	 */
	ControllerMetadata.prototype.afterApplySettings = function() {
		Metadata.prototype.afterApplySettings.call(this);
		var bIsExtension = this.isA("sap.ui.core.mvc.ControllerExtension");
		if (this._oParent) {
			var mParentMethods = this._oParent._mMethods ? this._oParent._mMethods : {};
			//allow change of visibility but not the other attributes
			for (var sMethod in mParentMethods) {
			if (this._mMethods[sMethod] && !bIsExtension) {
			var bPublic = this._mMethods[sMethod].public;
			//copy parent method definition as final/overrideExecution should not be overridden
			this._mMethods[sMethod] = merge({}, mParentMethods[sMethod]);
			if (bPublic !== undefined) {
			this._mMethods[sMethod].public = bPublic;
			}
			if (!this.isMethodPublic(sMethod) && this._mMethods[sMethod].public !== mParentMethods[sMethod].public) {
			//if visibility changed to private delete from public methods
			this._aAllPublicMethods.splice(this._aAllPublicMethods.indexOf(sMethod), 1);
			}
			} else {
			this._mMethods[sMethod] = mParentMethods[sMethod];
			}
			}
		}

		//flag each extension as final (but not the class ControllerExtension itself)
		if (this._oParent && this._oParent.isA("sap.ui.core.mvc.ControllerExtension")) {
		   this._bFinal = true;
		}
	};

	/**
	 * Returns the Namespace for a ControllerExtension. The Namespace is
	 * extracted from the ControllerExtensions name:
	 *

	 *   name: my.name.space.Extension
	 *   namespace = my.name.space
	 *

	 * @return {string} The Namespace
	 * @public
	 */
	ControllerMetadata.prototype.getNamespace = function() {
		var bIsAnonymous = this._sClassName.indexOf("anonymousExtension~") == 0;
		var sNamespace = bIsAnonymous ? this._oParent._sClassName : this._sClassName;
		return sNamespace.substr(0,sNamespace.lastIndexOf("."));
	};

	/**
	 * Check if method is flagged as final.
	 *
	 * @param {string} sMethod Name of the method
	 * @return {boolean} Whether the method is flagged as final or not
	 */
	ControllerMetadata.prototype.isMethodFinal = function(sMethod) {
		var oMethodMetadata = this._mMethods[sMethod];
		return oMethodMetadata && oMethodMetadata.final;
	};

	/**
	 * Check if method is flagged as public.
	 *
	 * @param {string} sMethod Name of the method
	 * @return {boolean} Whether the method is flagged as public or not
	 */
	ControllerMetadata.prototype.isMethodPublic = function(sMethod) {
		var oMethodMetadata = this._mMethods[sMethod];
		return oMethodMetadata && oMethodMetadata.public;
	};

	/**
	 * Get all defined methods and their metadata.
	 *
	 * @return {Object<string,object>} A map containing all methods (key) and their metadata
	 */
	ControllerMetadata.prototype.getAllMethods = function() {
		return this._mMethods;
	};

	/**
	 * Returns the override execution strategy for the given method.
	 *
	 * @param {string} sMethod Name of the method
	 * @return {sap.ui.core.mvc.OverrideExecution} The override execution strategy
	 */
	ControllerMetadata.prototype.getOverrideExecution = function(sMethod) {
		var oMethodMetadata = this._mMethods[sMethod];
		var sOverrideExecution = OverrideExecution.Instead;
		if (oMethodMetadata) {
			sOverrideExecution = oMethodMetadata.overrideExecution;
		}
		return sOverrideExecution;
	};

	 /**
	 * Returns the override definition of this extension.
	 *
	 * @return {object} The overrides
	 * @private
	 */
	ControllerMetadata.prototype.getOverrides = function() {
		return this._override;
	};

	 /**
	 * Returns the 'static' override definition registered by the override function for this extension
	 *
	 * @return {object} The static overrides
	 * @private
	 */
	ControllerMetadata.prototype.getStaticOverrides = function() {
		return this._staticOverride;
	};

	/**
	 * Checks whether override definitions exists.
	 *
	 * @return {boolean} Whether override definitions exists
	 * @private
	 */
	ControllerMetadata.prototype.hasOverrides = function() {
		return !!this._override || !!this._staticOverride;
	};

	/**
	 * Get configuration for extending lifecycle methods.
	 *
	 * @return {Object<string,object>} A map containing the lifecycle configuration metadata
	 * @private
	 */
	ControllerMetadata.prototype.getLifecycleConfiguration = function() {
		return this._defaultLifecycleMethodMetadata;
	};

	return ControllerMetadata;
});<|endoftext|>