<commit_before>var WebSocket = require('ws');
var EventEmitter = require('events').EventEmitter;
var formatter = require('./formatter');
var Response = require('./response').Response;

var SCSocket = function (id, server, socket, authData) {
  var self = this;

  EventEmitter.call(this);

  this._localEvents = {
    'open': 1,
    'close': 1,
    'disconnect': 1,
    'error': 1,
    'raw': 1
  };

  this._autoAckEvents = {
    'ready': 1,
    'publish': 1
  };

  this.id = id;
  this.server = server;
  this.socket = socket;
  this.state = this.OPEN;

  this.request = this.socket.upgradeReq;
  this.remoteAddress = this.request.remoteAddress;

  this._cid = 1;
  this._callbackMap = {};

  this._authToken = authData.token || null;

  this.socket.on('error', function (err) {
    EventEmitter.prototype.emit.call(self, 'error', err);
  });

  this.socket.on('close', function (code) {
    self._onSCClose(code);
  });

  this._pingIntervalTicker = setInterval(this._sendPing.bind(this), this.server.pingInterval);
  this._resetPingTimeout();

  // Receive incoming raw messages
  this.socket.on('message', function (message, flags) {
    self._resetPingTimeout();

    EventEmitter.prototype.emit.call(self, 'message', message);
    var json = formatter.parse(message);

    if (json == null) {
      var err = new Error('Received empty message');
      EventEmitter.prototype.emit.call(self, 'error', err);
    } else if (json.event) {
      if (self._localEvents[json.event] == null) {
        var response = new Response(self, json.cid);
        self.server.verifyEvent(self, json.event, json.data, function (err) {
          if (err) {
            response.error(err);
          } else {
            if (self._autoAckEvents[json.event]) {
              response.end();
              EventEmitter.prototype.emit.call(self, json.event, json.data);
            } else {
              EventEmitter.prototype.emit.call(self, json.event, json.data, response.callback.bind(response));
            }
          }
        });
      }
    } else if (json.cid == null) {
      if (json.disconnect) {
        self.state = self.CLOSING;
        EventEmitter.prototype.emit.call(self, 'disconnect', json.code, json.data);

      // In case of pong, do nothing
      } else if (!json.pong) {
        // If incoming message has no cid, then we emit it as raw data
        EventEmitter.prototype.emit.call(self, 'raw', message);
      }
    } else {
      // If incoming message is a response to a previously sent message
      var ret = self._callbackMap[json.cid];
      if (ret) {
        clearTimeout(ret.timeout);
        delete self._callbackMap[json.cid];
        ret.callback(json.error, json.data);
      }
    }
  });

  // Emit initial status to client
  this.emit('ready', {
    id: this.id,
    isAuthenticated: !!this._authToken,
    authError: authData.error,
    pingTimeout: this.server.pingTimeout
  });
};

SCSocket.prototype = Object.create(EventEmitter.prototype);

SCSocket.CONNECTING = SCSocket.prototype.CONNECTING = 'connecting';
SCSocket.OPEN = SCSocket.prototype.OPEN = 'open';
SCSocket.CLOSING = SCSocket.prototype.CLOSING = 'closing';
SCSocket.CLOSED = SCSocket.prototype.CLOSED = 'closed';

SCSocket.ignoreStatuses = {
  1000: 'Socket closed normally',
  1001: 'Socket hung up'
};

SCSocket.errorStatuses = {
  1002: 'A WebSocket protocol error was encountered',
  1003: 'Server terminated socket because it received invalid data',
  1006: 'Socket hung up',
  1007: 'Message format was incorrect',
  1008: 'Encountered a policy violation',
  1009: 'Message was too big to process',
  1010: 'Client ended the connection because the server did not comply with extension requirements',
  1011: 'Server encountered an unexpected fatal condition',
  4000: 'Server ping timed out',
  4001: 'Client pong timed out'
};

SCSocket.prototype._sendPing = function () {
  if (this.state == this.OPEN) {
    this.emitRaw({ping: 1});
  }
};

SCSocket.prototype._resetPingTimeout = function () {
  var self = this;

  clearTimeout(this._pingTimeoutTicker);
  this._pingTimeoutTicker = setTimeout(function() {
    self.socket.close(4001);
  }, this.server.pingTimeout);
};

SCSocket.prototype._nextCallId = function () {
  return this._cid++;
};

SCSocket.prototype.getState = function () {
  return this.state;
};

SCSocket.prototype.getBytesReceived = function () {
  return this.socket.bytesReceived;
};

SCSocket.prototype._onSCClose = function (code) {
  clearInterval(this._pingIntervalTicker);

  this.state = this.CLOSED;
  EventEmitter.prototype.emit.call(this, 'close', code);

  if (!SCSocket.ignoreStatuses[code]) {
    var err = new Error(SCSocket.errorStatuses[code] || 'Socket connection failed for unknown reasons');
    err.code = code;
    EventEmitter.prototype.emit.call(this, 'error', err);
  }
};

SCSocket.prototype.disconnect = function (code, data) {
  code = code || 1000;

  if (this.state == this.OPEN) {
    this.emitRaw({
      disconnect: 1,
      code: code,
      data: data
    });
    this.state = this.CLOSING;
    this.socket.close(code);
    EventEmitter.prototype.emit.call(this, 'disconnect', code, data);
  }
};

SCSocket.prototype.terminate = function () {
  this.socket.terminate();
};

SCSocket.prototype.send = function (data, options, callback) {
  var self = this;

  this.socket.send(data, options, function (err) {
    callback && callback(err);
    if (err) {
      EventEmitter.prototype.emit.call(self, 'error', err);
    }
  });
};

SCSocket.prototype.emitRaw = function (eventObject) {
  this.send(formatter.stringify(eventObject));
};

SCSocket.prototype.emit = function (event, data, callback) {
  var self = this;

  if (this._localEvents[event] == null) {
    var eventObject = {
      event: event
    };
    if (data !== undefined) {
      eventObject.data = data;
    }
    eventObject.cid = this._nextCallId();

    if (callback) {
      var timeout = setTimeout(function () {
        var error = new Error("Event response for '" + event + "' timed out");
        delete self._callbackMap[eventObject.cid];
        callback(error, eventObject);
      }, this.server.ackTimeout);

      this._callbackMap[eventObject.cid] = {callback: callback, timeout: timeout};
    }
    this.emitRaw(eventObject);
  } else {
    EventEmitter.prototype.emit.call(this, event, data);
  }
};

SCSocket.prototype.setAuthToken = function (data, options, callback) {
  this._authToken = data;
  var signedToken = this.server.auth.signToken(data, options);
  this.emit('setAuthToken', signedToken, callback);
};

SCSocket.prototype.getAuthToken = function () {
  return this._authToken;
};

SCSocket.prototype.removeAuthToken = function (callback) {
  this._authToken = null;
  this.emit('removeAuthToken', null, callback);
};

module.exports = SCSocket;<commit_msg>General improvements<commit_after>var WebSocket = require('ws');
var EventEmitter = require('events').EventEmitter;
var formatter = require('./formatter');
var Response = require('./response').Response;

var SCSocket = function (id, server, socket, authData) {
  var self = this;

  EventEmitter.call(this);

  this._localEvents = {
    'open': 1,
    'disconnect': 1,
    'error': 1,
    'raw': 1
  };

  this._autoAckEvents = {
    'ready': 1,
    'publish': 1
  };

  this.id = id;
  this.server = server;
  this.socket = socket;
  this.state = this.OPEN;

  this.request = this.socket.upgradeReq;
  this.remoteAddress = this.request.remoteAddress;

  this._cid = 1;
  this._callbackMap = {};

  this._authToken = authData.token || null;

  this.socket.on('error', function (err) {
    EventEmitter.prototype.emit.call(self, 'error', err);
  });

  this.socket.on('close', function (code, data) {
    if (code != 1000) {
      self._onSCClose(code, data);
    }
  });

  this._pingIntervalTicker = setInterval(this._sendPing.bind(this), this.server.pingInterval);
  this._resetPingTimeout();

  // Receive incoming raw messages
  this.socket.on('message', function (message, flags) {
    self._resetPingTimeout();

    EventEmitter.prototype.emit.call(self, 'message', message);
    var json = formatter.parse(message);

    if (json == null) {
      var err = new Error('Received empty message');
      EventEmitter.prototype.emit.call(self, 'error', err);
    } else if (json.event) {
      if (self._localEvents[json.event] == null) {
        var response = new Response(self, json.cid);
        self.server.verifyEvent(self, json.event, json.data, function (err) {
          if (err) {
            response.error(err);
          } else {
            if (self._autoAckEvents[json.event]) {
              response.end();
              EventEmitter.prototype.emit.call(self, json.event, json.data);
            } else {
              EventEmitter.prototype.emit.call(self, json.event, json.data, response.callback.bind(response));
            }
          }
        });
      }
    } else if (json.cid == null) {
      if (json.disconnect) {
        self._onSCClose(json.code, json.data);

      // In case of pong, do nothing
      } else if (!json.pong) {
        // If incoming message has no cid, then we emit it as raw data
        EventEmitter.prototype.emit.call(self, 'raw', message);
      }
    } else {
      // If incoming message is a response to a previously sent message
      var ret = self._callbackMap[json.cid];
      if (ret) {
        clearTimeout(ret.timeout);
        delete self._callbackMap[json.cid];
        ret.callback(json.error, json.data);
      }
    }
  });

  // Emit initial status to client
  this.emit('ready', {
    id: this.id,
    isAuthenticated: !!this._authToken,
    authError: authData.error,
    pingTimeout: this.server.pingTimeout
  });
};

SCSocket.prototype = Object.create(EventEmitter.prototype);

SCSocket.CONNECTING = SCSocket.prototype.CONNECTING = 'connecting';
SCSocket.OPEN = SCSocket.prototype.OPEN = 'open';
SCSocket.CLOSED = SCSocket.prototype.CLOSED = 'closed';

SCSocket.ignoreStatuses = {
  1000: 'Socket closed normally',
  1001: 'Socket hung up'
};

SCSocket.errorStatuses = {
  1002: 'A WebSocket protocol error was encountered',
  1003: 'Server terminated socket because it received invalid data',
  1006: 'Socket hung up',
  1007: 'Message format was incorrect',
  1008: 'Encountered a policy violation',
  1009: 'Message was too big to process',
  1010: 'Client ended the connection because the server did not comply with extension requirements',
  1011: 'Server encountered an unexpected fatal condition',
  4000: 'Server ping timed out',
  4001: 'Client pong timed out'
};

SCSocket.prototype._sendPing = function () {
  if (this.state == this.OPEN) {
    this.sendObject({ping: 1});
  }
};

SCSocket.prototype._resetPingTimeout = function () {
  var self = this;

  clearTimeout(this._pingTimeoutTicker);
  this._pingTimeoutTicker = setTimeout(function() {
    self._onSCClose(4001);
    self.socket.close(4001);
  }, this.server.pingTimeout);
};

SCSocket.prototype._nextCallId = function () {
  return this._cid++;
};

SCSocket.prototype.getState = function () {
  return this.state;
};

SCSocket.prototype.getBytesReceived = function () {
  return this.socket.bytesReceived;
};

SCSocket.prototype._onSCClose = function (code, data) {
  if (this.state != this.CLOSED) {
    clearInterval(this._pingIntervalTicker);

    this.state = this.CLOSED;
    EventEmitter.prototype.emit.call(this, 'disconnect', code, data);

    if (!SCSocket.ignoreStatuses[code]) {
      var err = new Error(SCSocket.errorStatuses[code] || 'Socket connection failed for unknown reasons');
      err.code = code;
      EventEmitter.prototype.emit.call(this, 'error', err);
    }
  }
};

SCSocket.prototype.disconnect = function (code, data) {
  code = code || 1000;

  if (this.state == this.OPEN) {
    this.sendObject({
      disconnect: 1,
      code: code,
      data: data
    });
    this.socket.close(code);
    this._onSCClose(code, data);
  }
};

SCSocket.prototype.terminate = function () {
  this.socket.terminate();
};

SCSocket.prototype.send = function (data, options, callback) {
  var self = this;

  this.socket.send(data, options, function (err) {
    callback && callback(err);
    if (err) {
      EventEmitter.prototype.emit.call(self, 'error', err);
    }
  });
};

SCSocket.prototype.sendObject = function (object) {
  this.send(formatter.stringify(object));
};

SCSocket.prototype.emit = function (event, data, callback) {
  var self = this;

  if (this._localEvents[event] == null) {
    var eventObject = {
      event: event
    };
    if (data !== undefined) {
      eventObject.data = data;
    }
    eventObject.cid = this._nextCallId();

    if (callback) {
      var timeout = setTimeout(function () {
        var error = new Error("Event response for '" + event + "' timed out");
        delete self._callbackMap[eventObject.cid];
        callback(error, eventObject);
      }, this.server.ackTimeout);

      this._callbackMap[eventObject.cid] = {callback: callback, timeout: timeout};
    }
    this.sendObject(eventObject);
  } else {
    EventEmitter.prototype.emit.call(this, event, data);
  }
};

SCSocket.prototype.setAuthToken = function (data, options, callback) {
  this._authToken = data;
  var signedToken = this.server.auth.signToken(data, options);
  this.emit('setAuthToken', signedToken, callback);
};

SCSocket.prototype.getAuthToken = function () {
  return this._authToken;
};

SCSocket.prototype.removeAuthToken = function (callback) {
  this._authToken = null;
  this.emit('removeAuthToken', null, callback);
};

module.exports = SCSocket;<|endoftext|>


<commit_before>import React, { PropTypes, Component } from 'react';
import { Tab as ReactTab, Tabs as ReactTabs, TabList, TabPanel } from 'react-tabs';
import { setContainers } from '../../../../dist/tab-history-library';
import { Match, Link } from 'react-router';
import './Tabs.css';
import TabMaster1 from "../components/TabMaster1";
import TabMaster2 from "../components/TabMaster2";
import TabMaster3 from "../components/TabMaster3";
import TabPage from "../components/TabPage";

const tabsConfig = [
  {initialUrl: '/tabs/1', urlPatterns: ['/tabs/1', '/tabs/1/*']},
  {initialUrl: '/tabs/2', urlPatterns: ['/tabs/2', '/tabs/2/*']},
  {initialUrl: '/tabs/3', urlPatterns: ['/tabs/3', '/tabs/3/*']}
];

const tabs = setContainers(tabsConfig);

export const Tab1 = () => (

    <Match exactly pattern="/tabs/1" component={TabMaster1} />
    <Match exactly pattern="/tabs/1/:page" component={TabPage} />

);

export const Tab2 = () => (

    <Match exactly pattern="/tabs/2" component={TabMaster2} />
    <Match exactly pattern="/tabs/2/:page" component={TabPage} />

);

export const Tab3 = () => (

    <Match exactly pattern="/tabs/3" component={TabMaster3} />
    <Match exactly pattern="/tabs/3/:page" component={TabPage} />

);

const onTabClick = (router, index) => {
  /*
  const containerPaths = getContainerPaths();
  const containerPath = `/tabs/${index + 1}`;
  if (isActiveTab(containerPath)) {
    router.replaceWith(containerPath);  // Go to the top path in this container
  }
  else {
  */
    tabs.switchTo(index);
  //}
};

class Tabs extends Component {

  constructor(props) {
    super(props);
    this.state = {
      currentTab: tabs.getActive()
    };
  }

  componentWillMount() {
    tabs.addChangeListener(state => {

      console.log(state.active);

      this.setState({currentTab: state.active});
    })
  }

  render() {
    const {router, match:matchContext} = this.props;
    const onClick = onTabClick.bind(null, router);
    return (

Tabs example

Each tab has its own individual history.

Clicking on an already active tab goes to the top of its history stack.

      <ReactTabs selectedIndex={this.state.currentTab.index}>
        <TabList>
          <ReactTab onClick={() => onClick(0)}>One</ReactTab>
          <ReactTab onClick={() => onClick(1)}>Two</ReactTab>
          <ReactTab onClick={() => onClick(2)}>Three</ReactTab>
        </TabList>
        <TabPanel>

            <Tab1 />

        </TabPanel>
        <TabPanel>

            <Tab2 />

        </TabPanel>
        <TabPanel>

            <Tab3 />

        </TabPanel>
      </ReactTabs>

);
  }
}

Tabs.contextTypes = {
  router: PropTypes.any.isRequired,
  match: PropTypes.any.isRequired
};

export default Tabs;<commit_msg>Fixed bug in replace push with replace, but need a "zero" page<commit_after>import React, { PropTypes, Component } from 'react';
import { Tab as ReactTab, Tabs as ReactTabs, TabList, TabPanel } from 'react-tabs';
import { setContainers } from '../../../../dist/tab-history-library';
import { Match, Link } from 'react-router';
import './Tabs.css';
import TabMaster1 from "../components/TabMaster1";
import TabMaster2 from "../components/TabMaster2";
import TabMaster3 from "../components/TabMaster3";
import TabPage from "../components/TabPage";

const tabsConfig = [
  {initialUrl: '/tabs/1', urlPatterns: ['/tabs/1', '/tabs/1/*']},
  {initialUrl: '/tabs/2', urlPatterns: ['/tabs/2', '/tabs/2/*']},
  {initialUrl: '/tabs/3', urlPatterns: ['/tabs/3', '/tabs/3/*']}
];

const tabs = setContainers(tabsConfig);

export const Tab1 = () => (

    <Match exactly pattern="/tabs/1" component={TabMaster1} />
    <Match exactly pattern="/tabs/1/:page" component={TabPage} />

);

export const Tab2 = () => (

    <Match exactly pattern="/tabs/2" component={TabMaster2} />
    <Match exactly pattern="/tabs/2/:page" component={TabPage} />

);

export const Tab3 = () => (

    <Match exactly pattern="/tabs/3" component={TabMaster3} />
    <Match exactly pattern="/tabs/3/:page" component={TabPage} />

);

const onTabClick = (router, index) => {
  /*
  const containerPaths = getContainerPaths();
  const containerPath = `/tabs/${index + 1}`;
  if (isActiveTab(containerPath)) {
    router.replaceWith(containerPath);  // Go to the top path in this container
  }
  else {
  */
    tabs.switchTo(index);
  //}
};

class Tabs extends Component {

  constructor(props) {
    super(props);
    this.state = {
      currentTab: tabs.getActive()
    };
  }

  componentWillMount() {
    tabs.addChangeListener(state => {
      this.setState({currentTab: state.active});
    })
  }

  render() {
    const {router, match:matchContext} = this.props;
    const onClick = onTabClick.bind(null, router);
    return (

Tabs example

Each tab has its own individual history.

Clicking on an already active tab goes to the top of its history stack.

      <ReactTabs selectedIndex={this.state.currentTab.index}>
        <TabList>
          <ReactTab onClick={() => onClick(0)}>One</ReactTab>
          <ReactTab onClick={() => onClick(1)}>Two</ReactTab>
          <ReactTab onClick={() => onClick(2)}>Three</ReactTab>
        </TabList>
        <TabPanel>

            <Tab1 />

        </TabPanel>
        <TabPanel>

            <Tab2 />

        </TabPanel>
        <TabPanel>

            <Tab3 />

        </TabPanel>
      </ReactTabs>

);
  }
}

Tabs.contextTypes = {
  router: PropTypes.any.isRequired,
  match: PropTypes.any.isRequired
};

export default Tabs;<|endoftext|>


<commit_before>var EOL = require('os').EOL;

module.exports = {
  name: 'ember-cli-mocha',

  normalizeEntityName: function() {
    // this prevents an error when the entityName is
    // not specified (since that doesn't actually matter
    // to us
  },

  afterInstall: function() {
    var addonContext = this;

    this.insertIntoFile('tests/.jshintrc',
      '    "mocha",' + EOL +
      '    "chai",' + EOL +
      '    "expect",' + EOL +
      '    "describe",' + EOL +
      '    "it",' + EOL +
      '    "before",' + EOL +
      '    "beforeEach",' + EOL +
      '    "after",' + EOL +
      '    "afterEach",',
      {after:'  "predef": ['+EOL});

    return this.addBowerPackageToProject('ember-mocha', '~0.4.0')
      .then(function() {
        return addonContext.addBowerPackageToProject('ember-cli/ember-cli-test-loader', '0.1.0');
      })
      .then(function() {
        return addonContext.addBowerPackageToProject('ember-cli/ember-cli-shims', '0.0.3');
      });
  }
};
<commit_msg>Remove Mocha-specific global declarations.<commit_after>var EOL = require('os').EOL;

module.exports = {
  name: 'ember-cli-mocha',

  normalizeEntityName: function() {
    // this prevents an error when the entityName is
    // not specified (since that doesn't actually matter
    // to us
  },

  afterInstall: function() {
    var addonContext = this;

    return this.addBowerPackageToProject('ember-mocha', '~0.4.0')
      .then(function() {
        return addonContext.addBowerPackageToProject('ember-cli/ember-cli-test-loader', '0.1.0');
      })
      .then(function() {
        return addonContext.addBowerPackageToProject('ember-cli/ember-cli-shims', '0.0.3');
      });
  }
};
<|endoftext|>


<commit_before>'use strict';

var DIR = './';
var exit = require(DIR + 'exit');

module.exports.root     = root;
module.exports.editor   = editor;

function root(dir) {
    var fs;

    if (dir !== '/') {
        fs  = require('fs');
        fs.stat(dir, function(error) {
            if (error)
                exit('cloudcmd --root: %s', error.message);
            else
                console.log('root:', dir);
        });
    }
}

function editor(name) {
    var reg = /^(dword|edward|deepword)$/;

    if (!reg.test(name))
        exit('cloudcmd --editor: could be "dword", "edward" or "deepword" only');
}

<commit_msg>refactor(validate) rm DIR<commit_after>'use strict';

var exit = require('./exit');

module.exports.root     = root;
module.exports.editor   = editor;

function root(dir) {
    var fs;

    if (dir !== '/') {
        fs  = require('fs');
        fs.stat(dir, function(error) {
            if (error)
                exit('cloudcmd --root: %s', error.message);
            else
                console.log('root:', dir);
        });
    }
}

function editor(name) {
    var reg = /^(dword|edward|deepword)$/;

    if (!reg.test(name))
        exit('cloudcmd --editor: could be "dword", "edward" or "deepword" only');
}

<|endoftext|>


<commit_before>import React from 'react'
import ReactDOM from 'react-dom'
import _ from 'lodash'

class Select extends React.Component {

  static propTypes = {
    code        : React.PropTypes.string,
    disabled    : React.PropTypes.bool,
    placeholder : React.PropTypes.string,
    defaultValue: React.PropTypes.oneOfType(React.PropTypes.string, React.PropTypes.string),
    asyncStatus : React.PropTypes.string
  }

  static defaultProps = {
    code        : null,
    disabled    : false,
    placeholder : '',
    defaultValue: '',
    allowBlank  : true,
    onChange    : () => {}
  }

  constructor(props) {
    super(props)
    this.NO_SELECTION     = `EMPTY_${Math.floor(Math.random() * 999999).toString(36)}`
    this.state            = {
      value: '',
      mounted: false
    }
    this.deferredSetValue = undefined
  }

  render() {
    let cssClass = 'ui search selection dropdown';
    if (this.props.asyncStatus === 'AWAITING') {
      cssClass += ' loading';
    }
    return (

        <input type="hidden" name={this.props.code} onChange={this.handleChange.bind(this)} />

        {(this.props.placeholder) ?
{this.props.placeholder}
 :

}
        {((elem) => {
          if(elem.props.options) {
            return (

              {this.props.allowBlank ?
No Selection
 : null}
              { elem.props.options.map((option, index) => {
                let icon = option.icon ? <i className={`ui icon ${option.icon}`}/> : null;
                return <div className="item" key={`option_${index}`} data-value={option.key}>{icon}{option.value}

              })}

            )
          }
        })(this)}

    )
  }

  componentDidMount() {
    $(this.refs.control).dropdown({
      onChange: this.handleChange.bind(this)
    })
    if (!this.props.allowBlank && !this.props.defaultValue) {
      $(this.refs.control).dropdown('set selected', _.get(_.first(this.props.options), 'key', null));
    }
    else {
      $(this.refs.control).dropdown('set selected', this.props.defaultValue);
    }
    //$(this.refs.control).dropdown('setting', 'onChange', this.handleChange.bind(this));
    $(this.refs.control).attr('tabIndex', 0)
    //this.setState({mounted: true})
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.asyncStatus !== this.props.asyncStatus && nextProps.asyncStatus !== 'AWAITING') {
      _.defer(() => this.setValue(this.deferredSetValue || this.props.defaultValue || null))
    }
  }

  componentDidUpdate() {
    $(this.refs.control).dropdown('destroy')
    $(this.refs.control).dropdown('setting', 'onChange', this.handleChange.bind(this));
  }

  getValue() {
    return $(this.refs.control).dropdown('get value') || this.props.defaultValue || null
  }

  setValue(value) {
    if (this.props.asyncStatus === 'AWAITING') {
      this.deferredSetValue = value
      return
    }
    $(this.refs.control).dropdown('setting', 'onChange', _.noop);
    $(this.refs.control).dropdown('set selected', value)
    $(this.refs.control).dropdown('setting', 'onChange', this.handleChange.bind(this));
  }

  handleChange(event) {
    if (this.getValue() === this.NO_SELECTION) {
      $(this.refs.control).dropdown('clear')
    }
    this.props.onChange(this.props.code, event)
    $(this.refs.control).find('.text > span').attr('data-reactid', null)
  }

  clearField() {
    $(this.refs.control).dropdown('setting', 'onChange', _.noop);
    $(this.refs.control).dropdown('clear')
    $(this.refs.control).dropdown('setting', 'onChange', this.handleChange.bind(this));
  }

  getReference() {
    return { [this.props.code]: this }
  }
}

export default Select
<commit_msg>Forcibly reset the placeholder text of Select elements when clearing their value<commit_after>import React from 'react'
import ReactDOM from 'react-dom'
import _ from 'lodash'

class Select extends React.Component {

  static propTypes = {
    code        : React.PropTypes.string,
    disabled    : React.PropTypes.bool,
    placeholder : React.PropTypes.string,
    defaultValue: React.PropTypes.oneOfType(React.PropTypes.string, React.PropTypes.string),
    asyncStatus : React.PropTypes.string
  }

  static defaultProps = {
    code        : null,
    disabled    : false,
    placeholder : '',
    defaultValue: '',
    allowBlank  : true,
    onChange    : () => {}
  }

  constructor(props) {
    super(props)
    this.NO_SELECTION     = `EMPTY_${Math.floor(Math.random() * 999999).toString(36)}`
    this.state            = {
      value: '',
      mounted: false
    }
    this.deferredSetValue = undefined
  }

  render() {
    let cssClass = 'ui search selection dropdown';
    if (this.props.asyncStatus === 'AWAITING') {
      cssClass += ' loading';
    }
    return (

        <input type="hidden" name={this.props.code} onChange={this.handleChange.bind(this)} />

        {(this.props.placeholder) ?
{this.props.placeholder}
 :

}
        {((elem) => {
          if(elem.props.options) {
            return (

              {this.props.allowBlank ?
No Selection
 : null}
              { elem.props.options.map((option, index) => {
                let icon = option.icon ? <i className={`ui icon ${option.icon}`}/> : null;
                return <div className="item" key={`option_${index}`} data-value={option.key}>{icon}{option.value}

              })}

            )
          }
        })(this)}

    )
  }

  componentDidMount() {
    $(this.refs.control).dropdown({
      onChange: this.handleChange.bind(this)
    })
    if (!this.props.allowBlank && !this.props.defaultValue) {
      $(this.refs.control).dropdown('set selected', _.get(_.first(this.props.options), 'key', null));
    }
    else {
      $(this.refs.control).dropdown('set selected', this.props.defaultValue);
    }
    //$(this.refs.control).dropdown('setting', 'onChange', this.handleChange.bind(this));
    $(this.refs.control).attr('tabIndex', 0)
    //this.setState({mounted: true})
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.asyncStatus !== this.props.asyncStatus && nextProps.asyncStatus !== 'AWAITING') {
      _.defer(() => this.setValue(this.deferredSetValue || this.props.defaultValue || null))
    }
  }

  componentDidUpdate() {
    $(this.refs.control).dropdown('destroy')
    $(this.refs.control).dropdown('setting', 'onChange', this.handleChange.bind(this));
  }

  getValue() {
    return $(this.refs.control).dropdown('get value') || this.props.defaultValue || null
  }

  setValue(value) {
    if (this.props.asyncStatus === 'AWAITING') {
      this.deferredSetValue = value
      return
    }
    $(this.refs.control).dropdown('setting', 'onChange', _.noop);
    $(this.refs.control).dropdown('set selected', value)
    $(this.refs.control).dropdown('setting', 'onChange', this.handleChange.bind(this));
  }

  handleChange(event) {
    if (this.getValue() === this.NO_SELECTION) {
      $(this.refs.control).dropdown('clear')
      $(this.refs.control).find('.default.text').html(this.props.placeholder || '')
    }
    this.props.onChange(this.props.code, event)
    $(this.refs.control).find('.text > span').attr('data-reactid', null)
  }

  clearField() {
    $(this.refs.control).dropdown('setting', 'onChange', _.noop);
    $(this.refs.control).dropdown('clear')
    $(this.refs.control).dropdown('setting', 'onChange', this.handleChange.bind(this));
  }

  getReference() {
    return { [this.props.code]: this }
  }
}

export default Select
<|endoftext|>


<commit_before>/*jshint regexp:false*/

/**
 * Controller abstract class.
 */
define([
    'dejavu/AbstractClass',
    './Joint',
    'services/state',
    'mout/lang/isFunction',
    'mout/lang/isString',
    'mout/string/startsWith',
    'mout/object/size',
    'has'
], function (AbstractClass, Joint, stateRegistry, isFunction, isString, startsWith, size, has) {

    'use strict';

    var Controller = AbstractClass.declare({
        $name: 'Controller',
        $extends: Joint,

        _states: null,
        _statesParams: null,
        _defaultState: null,
        _nrStates: 0,

        _currentState: null,

        /**
         * Constructor.
         */
        initialize: function () {
            var key,
                tmp,
                func,
                matches,
                regExp = this.$static._stateParamsRegExp;

            this._statesParams = {};
            this._states = this._states || {};
            this._nrStates = size(this._states);

            // Process the _states object
            for (key in this._states) {
                if (has('debug') && !key) {
                    throw new Error('Empty state detected in "' + this.$name + '".');
                }
                if (has('debug') && (key.indexOf('.') !== -1 || key.indexOf('/') !== -1)) {
                    throw new Error('States cannot contain dots or slashes (saw one in state "' + key + '" of "' + this.$name + '").');
                }

                // Process the params specified in the parentheses
                matches = key.match(regExp);
                if (matches) {
                    tmp = key.substr(0, key.indexOf('('));
                    this._states[tmp] = this._states[key];
                    delete this._states[key];
                    key = tmp;
                    this._statesParams[key] = matches[1].split(',');
                } else {
                    this._states[key] = this._states[key];
                }

                // Check if it is a string or already a function
                func = this._states[key];
                if (isString(func)) {
                    func = this[func];
                    if (has('debug') && !isFunction(func)) {
                        throw new Error('State handler "' + key + '" of "' + this.$name + '" references an unknown function.');
                    }
                    this._states[key] = func;
                }
            }

            // Process the default state
            if (has('debug') && this._defaultState && !this._states[this._defaultState]) {
                throw new Error('The default state of "' + this.$name + '" points to an unknown state.');
            }

            this.$super();
        },

        /**
         * Get the current state or null if none is set.
         *
         * @return {StateInterface} The state
         */
        getState: function () {
            return this._currentState;
        },

        /**
         * Generates an URL for a state.
         *
         * @param {String} state     The state name
         * @param {Object} [$params] The state params
         *
         * @return {String} The generated URL
         */
        generateUrl: function (state, $params) {
            return stateRegistry.generateUrl(this._resolveFullState(state), $params);
        },

        /**
         * Sets the current state.
         * If the state is the same, nothing happens.
         *
         * @param {String|Object|StateInterface} [$state]  The state name, the state parameter bag or a state instance
         * @param {Object}                       [$params] The state params to be used if the state is a string
         *
         * @return {Controller} The instance itself to allow chaining
         */
        setState: function ($state, $params) {
            var name,
                fullName;

            if (!this._nrStates) {
                return this;
            }

            // 1st - Try to make the state transition globally and only proceed if it didn't changed
            //       Also extract the local and full name of the state
            if (!$state || isString($state)) {
                // Resolve to the full name
                fullName = this._resolveFullState($state);

                // Change the state globally, and abort if actually changed
                if (stateRegistry.setCurrent(fullName, $params)) {
                    return this;
                }

                name = $state;
            } else {
                $state = $state.$state || $state;

                // Change the state globally, and abort if actually changed
                if (stateRegistry.setCurrent($state)) {
                    return this;
                }

                name = $state.getName();
                $params = $state.getParams();
            }

            $state = stateRegistry.getCurrent();
            $state.setParams($params);

            // 2nd - If no name is found check if we got a default state
            if (!name && !this._defaultState) {
                throw new Error('No default state defined in "' + this.$name + '".');
            }

            // 3rd - Check if the state of the controller actually changed
            if (!this._isSameState($state)) {
                this._performStateChange($state);
            // 4th - If not, propagate the state downwards
            } else {
                this._propagateState($state);
            }

            return this;
        },

        //////////////////////////////////////////////////////////////////

        /**
         * Checks if a given state is the same as the current controller state.
         *
         * @param {StateInterface} state The state
         *
         * @return {Boolean} True if the same, false otherwise
         */
        _isSameState: function (state) {
            if (!this._currentState) {
                return false;
            }

            var params = this._statesParams[state.getName()],
                isEqual;

            // Check if equal
            if (this._currentState.isEqual(state, params)) {
                return true;
            }

            // Check if equal when expanding the state to the default one
            if (!state.getName() && this._currentState.getName() === this._defaultState) {
                state.setFullName(state.getFullName() + '.' + this._defaultState);
                isEqual = this._currentState.isEqual(state, params);
                state.setFullName(this._defaultState);

                if (isEqual) {
                    return true;
                }
            }

            return false;
        },

        /**
         * Resolves a full state name.
         *
         * If starts with a / are absolute.
         * If starts with ../ are relative.
         * If empty will try to map to the default state.
         * Otherwise the full state name will be resolved from the local name.
         *
         * @param {String} [$name] The state name
         *
         * @return {String} The full state name
         */
        _resolveFullState: function ($name) {
            var matches,
                length,
                curr,
                currState,
                x;

            // TODO: we assume all the uplinks are controllers but this might not be true
            //       if the user implements a new class that extends from the Joint that
            //       are able to link to controllers

            // TODO: this function must be improved:
            //       - it must account for local names with a .

            // Absolute
            if ($name.charAt(0) === '/') {
                return $name.substr(1);
            }

            // Relative
            if (startsWith($name, '../')) {
                matches = $name.match(this.$static._relativeStateRegExp),
                length = matches.length - 1,
                curr = this;

                for (x = 1; x < length; x += 1) {
                    if (has('debug') && !curr._uplinks.length) {
                        throw new Error('Cannot generate relative path because "' + this.$name + '" has no uplinks.');
                    }

                    curr = curr._uplinks[0];
                }

                return curr._resolveFullState(matches[length] || null);
            }

            // Local
            curr = this;
            while (curr._uplinks.length) {
                curr = curr._uplinks[0];
                currState = curr.getState();
                if (!currState && has('debug')) {
                    throw new Error('Unable to resolve full state: "' + curr.$name + '" has no current state.');
                }
                $name = currState.getName() + ($name ? '.' + $name : '');
            }

            return $name;
        },

        /**
         * Performs the state change, calling the state handler if any.
         *
         * @param {StateInterface} state The state
         */
        _performStateChange: function (state) {
            this._currentState = state.clone();

            // Resolve to default state always
            if (!state.getName() && this._defaultState) {
                this._currentState.setFullName(state.getFullName() + '.' + this._defaultState);
            }

            var name = this._currentState.getName();
            state.next();

            if (this._states[name]) {
                this._states[name].call(this, state.getParams());
            } else if (has('debug')) {
                console.warn('Unhandled state "' + name + '" on controller "' + this.$name + '".');
            }
        },

        /**
         * Attempts to propagate the state to one of the downlinks.
         *
         * @param {StateInterface} state The state
         */
        _propagateState: function (state) {
            var name,
                curr,
                length,
                x;

            this._currentState.setParams(state.getParams());
            state.next();

            name = state.getName() || '';
            length = this._downlinks.length;

            for (x = 0; x < length; x += 1) {
                curr = this._downlinks[x];

                if (curr instanceof Controller) {
                    if (curr._states[name] || (!name && curr._defaultState)) {
                        curr.setState(state);
                        return;
                    }
                }
            }

            if (name && has('debug')) {
                console.warn('Could not propagate state "' + name + '" to any of the "' + this.$name + '" downlinks.');
            }
        },

        ////////////////////////////////////////////////////////////////

        $statics: {
            _stateParamsRegExp: /\((.+?)\)/,
            _relativeStateRegExp: /^(\.\.\/)+(.*)/
        }
    });

    return Controller;
});
<commit_msg>Unnecessary.<commit_after>/*jshint regexp:false*/

/**
 * Controller abstract class.
 */
define([
    'dejavu/AbstractClass',
    './Joint',
    'services/state',
    'mout/lang/isFunction',
    'mout/lang/isString',
    'mout/string/startsWith',
    'mout/object/size',
    'has'
], function (AbstractClass, Joint, stateRegistry, isFunction, isString, startsWith, size, has) {

    'use strict';

    var Controller = AbstractClass.declare({
        $name: 'Controller',
        $extends: Joint,

        _states: null,
        _statesParams: null,
        _defaultState: null,
        _nrStates: 0,

        _currentState: null,

        /**
         * Constructor.
         */
        initialize: function () {
            var key,
                tmp,
                func,
                matches,
                regExp = this.$static._stateParamsRegExp;

            this._statesParams = {};
            this._states = this._states || {};
            this._nrStates = size(this._states);

            // Process the _states object
            for (key in this._states) {
                if (has('debug') && !key) {
                    throw new Error('Empty state detected in "' + this.$name + '".');
                }
                if (has('debug') && (key.indexOf('.') !== -1 || key.indexOf('/') !== -1)) {
                    throw new Error('States cannot contain dots or slashes (saw one in state "' + key + '" of "' + this.$name + '").');
                }

                // Process the params specified in the parentheses
                matches = key.match(regExp);
                if (matches) {
                    tmp = key.substr(0, key.indexOf('('));
                    this._states[tmp] = this._states[key];
                    delete this._states[key];
                    key = tmp;
                    this._statesParams[key] = matches[1].split(',');
                } else {
                    this._states[key] = this._states[key];
                }

                // Check if it is a string or already a function
                func = this._states[key];
                if (isString(func)) {
                    func = this[func];
                    if (has('debug') && !isFunction(func)) {
                        throw new Error('State handler "' + key + '" of "' + this.$name + '" references an unknown function.');
                    }
                    this._states[key] = func;
                }
            }

            // Process the default state
            if (has('debug') && this._defaultState && !this._states[this._defaultState]) {
                throw new Error('The default state of "' + this.$name + '" points to an unknown state.');
            }

            this.$super();
        },

        /**
         * Get the current state or null if none is set.
         *
         * @return {StateInterface} The state
         */
        getState: function () {
            return this._currentState;
        },

        /**
         * Generates an URL for a state.
         *
         * @param {String} state     The state name
         * @param {Object} [$params] The state params
         *
         * @return {String} The generated URL
         */
        generateUrl: function (state, $params) {
            return stateRegistry.generateUrl(this._resolveFullState(state), $params);
        },

        /**
         * Sets the current state.
         * If the state is the same, nothing happens.
         *
         * @param {String|Object|StateInterface} [$state]  The state name, the state parameter bag or a state instance
         * @param {Object}                       [$params] The state params to be used if the state is a string
         *
         * @return {Controller} The instance itself to allow chaining
         */
        setState: function ($state, $params) {
            var name,
                fullName;

            if (!this._nrStates) {
                return this;
            }

            // 1st - Try to make the state transition globally and only proceed if it didn't changed
            //       Also extract the local and full name of the state
            if (!$state || isString($state)) {
                // Resolve to the full name
                fullName = this._resolveFullState($state);

                // Change the state globally, and abort if actually changed
                if (stateRegistry.setCurrent(fullName, $params)) {
                    return this;
                }

                name = $state;
            } else {
                $state = $state.$state || $state;

                // Change the state globally, and abort if actually changed
                if (stateRegistry.setCurrent($state)) {
                    return this;
                }

                name = $state.getName();
                $params = $state.getParams();
            }

            $state = stateRegistry.getCurrent();
            $state.setParams($params);

            // 2nd - If no name is found check if we got a default state
            if (!name && !this._defaultState) {
                throw new Error('No default state defined in "' + this.$name + '".');
            }

            // 3rd - Check if the state of the controller actually changed
            if (!this._isSameState($state)) {
                this._performStateChange($state);
            // 4th - If not, propagate the state downwards
            } else {
                this._propagateState($state);
            }

            return this;
        },

        //////////////////////////////////////////////////////////////////

        /**
         * Checks if a given state is the same as the current controller state.
         *
         * @param {StateInterface} state The state
         *
         * @return {Boolean} True if the same, false otherwise
         */
        _isSameState: function (state) {
            if (!this._currentState) {
                return false;
            }

            var params = this._statesParams[state.getName()],
                isEqual;

            // Check if equal
            if (this._currentState.isEqual(state, params)) {
                return true;
            }

            // Check if equal when expanding the state to the default one
            if (!state.getName() && this._currentState.getName() === this._defaultState) {
                state.setFullName(state.getFullName() + '.' + this._defaultState);
                isEqual = this._currentState.isEqual(state, params);
                state.setFullName(this._defaultState);

                if (isEqual) {
                    return true;
                }
            }

            return false;
        },

        /**
         * Resolves a full state name.
         *
         * If starts with a / are absolute.
         * If starts with ../ are relative.
         * If empty will try to map to the default state.
         * Otherwise the full state name will be resolved from the local name.
         *
         * @param {String} [$name] The state name
         *
         * @return {String} The full state name
         */
        _resolveFullState: function ($name) {
            var matches,
                length,
                curr,
                currState,
                x;

            // TODO: we assume all the uplinks are controllers but this might not be true
            //       if the user implements a new class that extends from the Joint that
            //       are able to link to controllers

            // TODO: this function must be improved:
            //       - it must account for local names with a .

            // Absolute
            if ($name.charAt(0) === '/') {
                return $name.substr(1);
            }

            // Relative
            if (startsWith($name, '../')) {
                matches = $name.match(this.$static._relativeStateRegExp),
                length = matches.length - 1,
                curr = this;

                for (x = 1; x < length; x += 1) {
                    if (has('debug') && !curr._uplinks.length) {
                        throw new Error('Cannot generate relative path because "' + this.$name + '" has no uplinks.');
                    }

                    curr = curr._uplinks[0];
                }

                return curr._resolveFullState(matches[length] || null);
            }

            // Local
            curr = this;
            while (curr._uplinks.length) {
                curr = curr._uplinks[0];
                currState = curr.getState();
                if (!currState && has('debug')) {
                    throw new Error('Unable to resolve full state: "' + curr.$name + '" has no current state.');
                }
                $name = currState.getName() + ($name ? '.' + $name : '');
            }

            return $name;
        },

        /**
         * Performs the state change, calling the state handler if any.
         *
         * @param {StateInterface} state The state
         */
        _performStateChange: function (state) {
            this._currentState = state.clone();

            // Resolve to default state always
            if (!state.getName() && this._defaultState) {
                this._currentState.setFullName(state.getFullName() + '.' + this._defaultState);
            }

            var name = this._currentState.getName();
            state.next();

            if (this._states[name]) {
                this._states[name].call(this, state.getParams());
            } else if (has('debug')) {
                console.warn('Unhandled state "' + name + '" on controller "' + this.$name + '".');
            }
        },

        /**
         * Attempts to propagate the state to one of the downlinks.
         *
         * @param {StateInterface} state The state
         */
        _propagateState: function (state) {
            var name,
                curr,
                length,
                x;

            this._currentState.setParams(state.getParams());
            state.next();

            name = state.getName();
            length = this._downlinks.length;

            for (x = 0; x < length; x += 1) {
                curr = this._downlinks[x];

                if (curr instanceof Controller) {
                    if (curr._states[name] || (!name && curr._defaultState)) {
                        curr.setState(state);
                        return;
                    }
                }
            }

            if (name && has('debug')) {
                console.warn('Could not propagate state "' + name + '" to any of the "' + this.$name + '" downlinks.');
            }
        },

        ////////////////////////////////////////////////////////////////

        $statics: {
            _stateParamsRegExp: /\((.+?)\)/,
            _relativeStateRegExp: /^(\.\.\/)+(.*)/
        }
    });

    return Controller;
});
<|endoftext|>


<commit_before>/*jshint regexp:false*/

/**
 * Controller abstract class.
 */
define([
    './Joint',
    'services/state',
    'mout/string/startsWith',
    'mout/object/size',
    'mout/object/pick',
    'mout/object/mixIn',
    'mout/object/fillIn',
    'mout/array/find',
    'has'
], function (Joint, stateRegistry, startsWith, size, pick, mixIn, fillIn, find, has) {

    'use strict';

    /**
     * Constructor.
     */
    function Controller() {
        Joint.call(this);

        this._parseStates();
        this._parseDefaultState();
    }

    Controller.prototype = Object.create(Joint.prototype);
    Controller.prototype.constructor = Controller;

    /**
     * Get the current state or null if none is set.
     *
     * @return {State} The state
     */
    Controller.prototype.getState = function () {
        return this._currentState;
    };

    /**
     * Generates an URL for a state.
     *
     * @param {String}  name       The state name
     * @param {Object}  [params]   The state params
     * @param {Boolean} [absolute] True to generate an absolute URL, false otherwise
     *
     * @return {String} The generated URL
     */
    Controller.prototype.generateUrl = function (name, params, absolute) {
        var state;

        // Resolve the state
        state = this._resolveFullState(name);
        mixIn(state.params, params);

        return stateRegistry.generateUrl(state.fullName, state.params, absolute);
    };

    /**
     * Sets the current state.
     * If the state is the same, nothing happens.
     *
     * @param {String} [name]    The state name
     * @param {Object} [params]  The state params
     * @param {Object} [options] The options
     *
     * @return {Controller} The instance itself to allow chaining
     */
    Controller.prototype.setState = function (name, params, options) {
        var stateMeta,
            state;

        // Resolve the state
        stateMeta = this._resolveFullState(name);
        mixIn(stateMeta.params, params);

        // If the state is absolute, simply set it on the state registry
        if (stateMeta.name == null) {
            stateRegistry.setCurrent(stateMeta.fullName, stateMeta.params, options);
            return this;
        }

        // Check if the state is globally registered
        if (stateRegistry.isRegistered(stateMeta.fullName)) {
            // If so attempt to change the global state, aborting if it succeeded
            if (stateRegistry.setCurrent(stateMeta.fullName, stateMeta.params, options)) {
                return this;
            }

            // Since the global state is equal, grab it to avoid creating unnecessary
            // state objects
            state = stateRegistry.getCurrent().seekTo(stateMeta.name);
        } else {
            state = stateRegistry._createStateInstance(stateMeta.name, stateMeta.params);

            // Generate local metadata
            state.getParams().$info = {
                newState: state,
                previousState: this._previousState
            };
        }

        return this.delegateState(state);
    };

    /**
     * Delegates a state to be handled by the controller.
     *
     * @param {Object|State} state The state parameter bag or a state instance
     *
     * @return {Controller} The instance itself to allow chaining
     */
    Controller.prototype.delegateState = function (state) {
        var name,
            currentState;

        // Assume app state if not passed
        if (state.$info) {
            state = state.$info.newState;
        }

        // Ensure state is filled with the defaults
        this._fillStateIfEmpty(state);

        name = state.getName();

        // If still has no name it means there's no default state defined
        if (!name) {
            if (has('debug') && this._nrStates) {
                console.warn('[spoonjs] No default state defined in "' + this.$name + '".');
            }

            return;
        }

        // Check if state exists
        if (!this._states[name]) {
            if (has('debug')) {
                console.warn('[spoonjs] Unknown state "' + name + '" on controller "' + this.$name + '".');
            }

            return;
        }

        // If the current state is not the same, transition to it
        if (!this._isSameState(state)) {
            this._performStateChange(state);
        // Otherwise propagate it to child controllers
        } else {
            this._propagateState(state);
        }

        // Sync up the full state name with the application one
        // This is needed because default states might have been translated down the chain
        // Note that the current state might not be set or be changed meanwhile if the user
        // override "_performStateChange()" or "_propagateState()"
        currentState = this._currentState;
        if (stateRegistry.getCurrent() === state && currentState && currentState.getName() === name) {
            this._currentState.setFullName(state.getFullName());
        }

        return this;
    };

    /**
     * Instruct the extend to merge states.
     *
     * {@inheritDoc}
     */
    Controller.extend = function (parent, props, merge) {
        merge = merge || [];
        merge.push('_states');

        return Joint.extend.call(this, parent, props, merge);
    };

    // --------------------------------------------

    /**
     * Fills the state object with the default state if it's name is empty.
     *
     * @param {State} state The state
     */
    Controller.prototype._fillStateIfEmpty = function (state) {
        if (!this._defaultState) {
            return;
        }

        if (state.getName() === this._defaultState.name) {
            fillIn(state.getParams(), this._defaultState.params);
        } else if (!state.getName()) {
            state.setFullName(state.getFullName() + '.' + this._defaultState.name);
            fillIn(state.getParams(), this._defaultState.params);
        }
    };

    /**
     * Parses the controller states.
     */
    Controller.prototype._parseStates = function () {
        var key,
            func,
            matches,
            regExp = this.constructor._stateParamsRegExp || Controller._stateParamsRegExp,
            states = this._states;

        this._states = {};
        this._nrStates = size(states);

        // Process the states object
        for (key in states) {
            func = states[key];

            // Process the params specified in the parentheses
            matches = key.match(regExp);
            if (matches) {
                key = key.substr(0, key.indexOf('('));
                this._states[key] = {};

                // If user specified state(*), then the state changes every time
                // even if the params haven't changed
                if (matches[1] === '*') {
                    this._states[key].wildcard = true;
                } else {
                    this._states[key].params = matches[1].split(/\s*,\s*/);
                }
            } else {
                this._states[key] = {};
            }

            if (has('debug')) {
                if (!stateRegistry.isValid(key)) {
                    throw new Error('State name "' + key + '" of "' + this.$name + '" has an invalid format.');
                }
                if (key.indexOf('.') !== -1) {
                    throw new Error('State name "' + key + '" of "' + this.$name + '" must be local (cannot contain dots).');
                }
            }

            // Check if it is a string or already a function
            if (typeof func === 'string') {
                func = this[func];
                this._states[key].fn = func;
            }

            if (has('debug') && typeof func !== 'function') {
                throw new Error('State handler "' + key + '" of "' + this.$name + '" references a nonexistent function.');
            }

            this._states[key].fn = func;
            this._states[key].params = this._states[key].params || [];
        }
    };

    /**
     * Parse the default state.
     */
    Controller.prototype._parseDefaultState = function () {
        // Convert default state as a string to an object
        if (typeof this._defaultState === 'string') {
            this._defaultState = {
                name: this._defaultState,
                params: {}
            };
        }

        if (has('debug') && this._defaultState) {
            if (!this._defaultState.name) {
                throw new Error('The default state of "' + this.$name + '" cannot be empty.');
            }
            if (!this._states[this._defaultState.name]) {
                throw new Error('The default state of "' + this.$name + '" points to an nonexistent state.');
            }
        }
    },

    /**
     * Resolves a full state name.
     *
     * If name starts with a / then state is absolute.
     * If name starts with ../ then state is relative.
     * If empty will try to map to the default state.
     * Otherwise the full state name will be resolved from the local name.
     *
     * @param {String} [name] The state name
     *
     * @return {Object} The full state name and params
     */
    Controller.prototype._resolveFullState = function (name) {
        var state,
            ancestor,
            ancestorState;

        name = name || '';

        // Absolute
        if (name.charAt(0) === '/') {
            return {
                fullName: name.substr(1),
                params: {}
            };
        }

        // Relative
        if (startsWith(name, '../')) {
            if (has('debug') && (!this._uplink || !(this._uplink instanceof Controller))) {
                throw new Error('Cannot resolve relative state "' + name + '" in "' + this.$name + '".');
            }

            state = this._uplink._resolveFullState(name.substr(3));
            delete state.name;  // Remove name because state is not local

            return state;
        }

        state = {
            name: name,
            fullName: name,
            params: {}
        };

        // Local
        ancestor = this._uplink;
        while (ancestor && ancestor instanceof Controller) {
            ancestorState = ancestor.getState();
            if (!ancestorState) {
                // Break here, the ancestor is not in any state
                break;
            }

            // Concatenate name & mix in relevant params
            state.fullName = ancestorState.getName() + (state.fullName ? '.' + state.fullName : '');
            fillIn(state.params, ancestor._currentStateParams);

            ancestor = ancestor._uplink;
        }

        // If no state name is set, use default state
        if (!state.name && this._defaultState) {
            state.name = this._defaultState.name;
            state.fullName = state.fullName || this._defaultState.name;
            mixIn(state.params, this._defaultState.params);
        }

        return state;
    };

    /**
     * Checks if a given state is the same as the current controller state.
     *
     * @param {State} state       The state
     * @param {State} [baseState] The state to compare against, defaults to the current state
     *
     * @return {Boolean} True if the same, false otherwise
     */
    Controller.prototype._isSameState = function (state, baseState) {
        var stateMeta;

        baseState = baseState || this._currentState;

        if (!baseState) {
            return false;
        }

        stateMeta = this._states[state.getName()] || {};

        // Check if state is a wildcard
        if (stateMeta.wildcard) {
            return false;
        }

        // Check if equal
        return baseState.isEqual(state, stateMeta.params);
    };

    /**
     * Sets the current state based on the passed in state.
     * Updates all the necessary properties used internally.
     *
     * @param {State} state The state
     */
    Controller.prototype._setCurrentState = function (state) {
        var params;

        // Update current state
        this._previousState = this._currentState;
        this._currentState = state.clone();
        this._currentStateParams = pick(this._currentState.getParams(), this._states[this._currentState.getName()].params);

        // Ensure $info gets up to date
        params = this._currentState.getParams();
        if (params.$info) {
            params.$info.newState = this._currentState;
        }

        // Update the state registry one
        if (state === stateRegistry.getCurrent() && state.getFullName() !== this._currentState.getFullName()) {
            state.setFullName(this._currentState.getFullName());
        }
    };

    /**
     * Performs the state change, calling the state handler if any.
     *
     * @param {State} state The state
     */
    Controller.prototype._performStateChange = function (state) {
        var stateMeta;

        // Update internal state
        this._setCurrentState(state);

        // Advance pointer
        state.next();

        // Execute handler
        stateMeta = this._states[this._currentState.getName()];
        stateMeta.fn.call(this, state.getParams());
    };

    /**
     * Attempts to propagate the state to one of the downlinks.
     *
     * @param {State} state The state
     */
    Controller.prototype._propagateState = function (state) {
        var name,
            leadingName,
            curr,
            length,
            x;

        // Update internal state
        this._setCurrentState(state);

        // Advance pointer
        state.next();

        // Find suitable child controller to handle the state
        name = state.getName();
        leadingName = state.getLeadingName();
        length = this._downlinks.length;

        // The first cycle on the down links are more strict to ensure we
        // don't match the wrong child
        for (x = 0; x < length; x += 1) {
            curr = this._downlinks[x];

            if (!(curr instanceof Controller)) {
                continue;
            }

            if (!name) {
                if (curr._defaultState && stateRegistry.isRegistered(state.getFullName() + '.' + curr._defaultState.name)) {
                    return curr.delegateState(state);
                }
            } else if (curr._currentState && curr._currentState.getLeadingName() === leadingName) {
                return curr.delegateState(state);
            }
        }

        // The second cycle is loose and only works if we got a state
        if (name) {
            for (x = 0; x < length; x += 1) {
                curr = this._downlinks[x];

                if (!(curr instanceof Controller)) {
                    continue;
                }

                if (curr._states[name]) {
                    return curr.delegateState(state);
                }
            }

            if (has('debug')) {
                console.warn('[spoonjs] No child controller of "' + this.$name + '" can handle the "' + name + '" state.');
            }
        }
    };

    Controller._stateParamsRegExp = /\((.+?)\)/;

    return Controller;
});
<commit_msg>Add internal flagging of states with !.<commit_after>/*jshint regexp:false*/

/**
 * Controller abstract class.
 */
define([
    './Joint',
    'services/state',
    'mout/string/startsWith',
    'mout/object/size',
    'mout/object/pick',
    'mout/object/fillIn',
    'mout/object/mixIn',
    'has'
], function (Joint, stateRegistry, startsWith, size, pick, fillIn, mixIn, has) {

    'use strict';

    /**
     * Constructor.
     */
    function Controller() {
        Joint.call(this);

        this._parseStates();
        this._parseDefaultState();
    }

    Controller.prototype = Object.create(Joint.prototype);
    Controller.prototype.constructor = Controller;

    /**
     * Get the current state or null if none is set.
     *
     * @return {State} The state
     */
    Controller.prototype.getState = function () {
        return this._currentState;
    };

    /**
     * Generates an URL for a state.
     *
     * @param {String}  name       The state name
     * @param {Object}  [params]   The state params
     * @param {Boolean} [absolute] True to generate an absolute URL, false otherwise
     *
     * @return {String} The generated URL
     */
    Controller.prototype.generateUrl = function (name, params, absolute) {
        var resolved;

        // Resolve the state
        resolved = this._resolveState(name, params);

        if (resolved.internal) {
            throw new Error('Cannot generate a URL for internal state "' + name + '" in "' + this.$name + '".');
        }

        return stateRegistry.generateUrl(resolved.fullName, resolved.params, absolute);
    };

    /**
     * Sets the current state.
     * If the state is the same, nothing happens.
     *
     * @param {String} [name]    The state name
     * @param {Object} [params]  The state params
     * @param {Object} [options] The options
     *
     * @return {Controller} The instance itself to allow chaining
     */
    Controller.prototype.setState = function (name, params, options) {
        var resolved,
            state;

        // Resolve the state
        // Note that parameters are shallow cloned to avoid side-effects
        resolved = this._resolveState(name, mixIn({}, params));

        // If the state is not ours, simply set it on the state registry
        if (resolved.absolute || resolved.relative) {
            stateRegistry.setCurrent(resolved.fullName, resolved.params, options);
            return this;
        }

        // Act differently if this state is global
        if (resolved.global) {
            // If so attempt to change the global state, aborting if it succeeded
            if (stateRegistry.setCurrent(resolved.fullName, resolved.params, options)) {
                return this;
            }

            // Since the global state is equal, grab it to avoid creating unnecessary
            // state objects
            state = stateRegistry.getCurrent().seekTo(resolved.name);
        // Otherwise state is local
        } else {
            state = stateRegistry._createStateInstance(resolved.name, resolved.params);

            // Generate local metadata
            state.getParams().$info = {
                newState: state,
                previousState: this._previousState
            };
        }

        return this.delegateState(state);
    };

    /**
     * Delegates a state to be handled by the controller.
     *
     * @param {Object|State} state The state parameter bag or a state instance
     *
     * @return {Controller} The instance itself to allow chaining
     */
    Controller.prototype.delegateState = function (state) {
        var name,
            currentState;

        // Assume app state if not passed
        if (state.$info) {
            state = state.$info.newState;
        }

        // Ensure state is filled with the defaults
        this._fillStateIfEmpty(state);

        name = state.getName();

        // If still has no name it means there's no default state defined
        if (!name) {
            if (has('debug') && this._nrStates) {
                console.warn('[spoonjs] No default state defined in "' + this.$name + '".');
            }

            return;
        }

        // Check if state exists
        if (!this._states[name]) {
            if (has('debug')) {
                console.warn('[spoonjs] Unknown state "' + name + '" on controller "' + this.$name + '".');
            }

            return;
        }

        // If the current state is not the same, transition to it
        if (!this._isSameState(state)) {
            this._performStateChange(state);
        // Otherwise propagate it to child controllers
        } else {
            this._propagateState(state);
        }

        // Sync up the full state name with the application one
        // This is needed because default states might have been translated down the chain
        // Note that the current state might not be set or be changed meanwhile if the user
        // override "_performStateChange()" or "_propagateState()"
        currentState = this._currentState;
        if (stateRegistry.getCurrent() === state && currentState && currentState.getName() === name) {
            this._currentState.setFullName(state.getFullName());
        }

        return this;
    };

    /**
     * Instruct the extend to merge states.
     *
     * {@inheritDoc}
     */
    Controller.extend = function (parent, props, merge) {
        merge = merge || [];
        merge.push('_states');

        return Joint.extend.call(this, parent, props, merge);
    };

    // --------------------------------------------

    /**
     * Fills the state object with the default state if it's name is empty.
     *
     * @param {State} state The state
     */
    Controller.prototype._fillStateIfEmpty = function (state) {
        if (!this._defaultState) {
            return;
        }

        if (state.getName() === this._defaultState.name) {
            fillIn(state.getParams(), this._defaultState.params);
        } else if (!state.getName()) {
            state.setFullName(state.getFullName() + '.' + this._defaultState.name);
            fillIn(state.getParams(), this._defaultState.params);
        }
    };

    /**
     * Parses the controller states.
     */
    Controller.prototype._parseStates = function () {
        var key,
            func,
            matches,
            regExp = this.constructor._stateParamsRegExp || Controller._stateParamsRegExp,
            states = this._states,
            internal;

        this._states = {};
        this._nrStates = size(states);

        // Process the states object
        for (key in states) {
            func = states[key];

            // Check if this state is internal
            if (!key.indexOf('!')) {
                key = key.substr(1);
                internal = true;
            } else {
                internal = false;
            }

            // Process the params specified in the parentheses
            matches = key.match(regExp);
            if (matches) {
                key = key.substr(0, key.indexOf('('));
                this._states[key] = { internal: internal };

                // If user specified state(*), then the state changes every time
                // even if the params haven't changed
                if (matches[1] === '*') {
                    this._states[key].wildcard = true;
                } else {
                    this._states[key].params = matches[1].split(/\s*,\s*/);
                }
            } else {
                this._states[key] = { internal: internal };
            }

            if (has('debug')) {
                if (!stateRegistry.isValid(key)) {
                    throw new Error('State name "' + key + '" of "' + this.$name + '" has an invalid format.');
                }
                if (key.indexOf('.') !== -1) {
                    throw new Error('State name "' + key + '" of "' + this.$name + '" must be local (cannot contain dots).');
                }
            }

            // Check if it is a string or already a function
            if (typeof func === 'string') {
                func = this[func];
                this._states[key].fn = func;
            }

            if (has('debug') && typeof func !== 'function') {
                throw new Error('State handler "' + key + '" of "' + this.$name + '" references a nonexistent function.');
            }

            this._states[key].fn = func;
            this._states[key].params = this._states[key].params || [];
        }
    };

    /**
     * Parse the default state.
     */
    Controller.prototype._parseDefaultState = function () {
        // Convert default state as a string to an object
        if (typeof this._defaultState === 'string') {
            this._defaultState = {
                name: this._defaultState,
                params: {}
            };
        }

        if (has('debug') && this._defaultState) {
            if (!this._defaultState.name) {
                throw new Error('The default state of "' + this.$name + '" cannot be empty.');
            }
            if (!this._states[this._defaultState.name]) {
                throw new Error('The default state of "' + this.$name + '" points to an nonexistent state.');
            }
        }
    },

    /**
     * Resolves a state.
     *
     * If name starts with a / then state is absolute.
     * If name starts with ../ then state is relative.
     * If empty will try to map to the default state.
     * If not empty, strict validation will be made against the name
     *
     * The full state name will be resolved by navigating through the ancestors,
     * unless the state is flagged as internal.
     *
     * @param {String} [name]   The state name
     * @param {Object} [params] The state params
     *
     * @return {Object} An object with the name, params and some flags
     */
    Controller.prototype._resolveState = function (name, params) {
        var resolved,
            ancestor,
            fullName,
            localName,
            ancestorState;

        name = name || '';
        params = params || {};

        // Absolute
        if (name.charAt(0) === '/') {
            name = name.substr(1);

            if (has('debug') && !stateRegistry.isRegistered(name)) {
                throw new Error('Resolved "' + name + '" as absolute but its not registered in the state registry.');
            }

            return { fullName: name, params: params, absolute: true };
        }

        // Relative which will be resolved
        if (startsWith(name, '../')) {
            if (has('debug') && (!this._uplink || !(this._uplink instanceof Controller))) {
                throw new Error('Cannot resolve relative state "' + name + '" in "' + this.$name + '" because controller has no parent.');
            }

            resolved = this._uplink._resolveState(name.substr(3), params);
            resolved.relative = true;
            delete resolved.name;

            if (has('debug') && !resolved.global) {
                throw new Error('Relative state "' + name + '" in "' + this.$name + '" is not a global state (only global are supported for now).');
            }

            return resolved;
        }

        // Local
        // Fill in with the default state
        if (!name && this._defaultState) {
            name = this._defaultState.name;
            params = fillIn(params, this._defaultState.params);
        }

        // If name was specified, do some special stuff
        if (name) {
            localName = name.split('.')[0];

            // Validate if state exists
            if (has('debug') && !this._states[localName]) {
                throw new Error('Unknown state "' + localName + '" in "' + this.$name + '".');
            }

            // Is this an internal state? If so end it here..
            if (this._states[localName].internal) {
                return { name: name, params: params, internal: true };
            }
        }

        // If we end up here, then we need to build the full name based on the ancestors
        fullName = name;

        if (this._uplink) {
            ancestor = this._uplink;

            while (ancestor && ancestor instanceof Controller) {
                ancestorState = ancestor.getState();

                if (!ancestorState) {
                    throw new Error('Unable to resolve full state when controller "' + this.$name + '" is not in any state.');
                }

                // Concatenate name & mix in relevant params
                fullName = ancestorState.getName() + (fullName ? '.' + fullName : '');
                fillIn(params, ancestor._currentStateParams);

                ancestor = ancestor._uplink;
            }
        }

        return { fullName: fullName, name: name, params: params, global: stateRegistry.isRegistered(fullName) };
    };

    /**
     * Checks if a given state is the same as the current controller state.
     *
     * @param {State} state       The state
     * @param {State} [baseState] The state to compare against, defaults to the current state
     *
     * @return {Boolean} True if the same, false otherwise
     */
    Controller.prototype._isSameState = function (state, baseState) {
        var stateMeta;

        baseState = baseState || this._currentState;

        if (!baseState) {
            return false;
        }

        stateMeta = this._states[state.getName()] || {};

        // Check if state is a wildcard
        if (stateMeta.wildcard) {
            return false;
        }

        // Check if equal
        return baseState.isEqual(state, stateMeta.params);
    };

    /**
     * Sets the current state based on the passed in state.
     * Updates all the necessary properties used internally.
     *
     * @param {State} state The state
     */
    Controller.prototype._setCurrentState = function (state) {
        var params;

        // Update current state
        this._previousState = this._currentState;
        this._currentState = state.clone();
        this._currentStateParams = pick(this._currentState.getParams(), this._states[this._currentState.getName()].params);

        // Ensure $info gets up to date
        params = this._currentState.getParams();
        if (params.$info) {
            params.$info.newState = this._currentState;
        }

        // Update the state registry one
        if (state === stateRegistry.getCurrent() && state.getFullName() !== this._currentState.getFullName()) {
            state.setFullName(this._currentState.getFullName());
        }
    };

    /**
     * Performs the state change, calling the state handler if any.
     *
     * @param {State} state The state
     */
    Controller.prototype._performStateChange = function (state) {
        var stateMeta;

        // Update internal state
        this._setCurrentState(state);

        // Advance pointer
        state.next();

        // Execute handler
        stateMeta = this._states[this._currentState.getName()];
        stateMeta.fn.call(this, state.getParams());
    };

    /**
     * Attempts to propagate the state to one of the downlinks.
     *
     * @param {State} state The state
     */
    Controller.prototype._propagateState = function (state) {
        var name,
            leadingName,
            curr,
            length,
            x;

        // Update internal state
        this._setCurrentState(state);

        // Advance pointer
        state.next();

        // Find suitable child controller to handle the state
        name = state.getName();
        leadingName = state.getLeadingName();
        length = this._downlinks.length;

        // The first cycle on the down links are more strict to ensure we
        // don't match the wrong child
        for (x = 0; x < length; x += 1) {
            curr = this._downlinks[x];

            if (!(curr instanceof Controller)) {
                continue;
            }

            if (!name) {
                if (curr._defaultState && stateRegistry.isRegistered(state.getFullName() + '.' + curr._defaultState.name)) {
                    return curr.delegateState(state);
                }
            } else if (curr._currentState && curr._currentState.getLeadingName() === leadingName) {
                return curr.delegateState(state);
            }
        }

        // The second cycle is loose and only works if we got a state
        if (name) {
            for (x = 0; x < length; x += 1) {
                curr = this._downlinks[x];

                if (!(curr instanceof Controller)) {
                    continue;
                }

                if (curr._states[name]) {
                    return curr.delegateState(state);
                }
            }

            if (has('debug')) {
                console.warn('[spoonjs] No child controller of "' + this.$name + '" can handle the "' + name + '" state.');
            }
        }
    };

    Controller._stateParamsRegExp = /\((.+?)\)/;

    return Controller;
});
<|endoftext|>