<commit_before>import React, { Component, PropTypes } from 'react'
import {
  Button,
  Card,
  CardBlock,
  CardTitle,
  CardSubtitle,
  Col,
  Modal,
  ModalHeader,
  ModalBody,
  ModalFooter
} from 'reactstrap'
import Form from 'react-jsonschema-form'
import ConfirmRejectModal from '../ConfirmRejectModal/ConfirmRejectModal'
import CustomFieldTemplate from './CustomFieldTemlpate'
import CustomArrayTemplate from './CustomArrayTemplate'
import SynonymsField from './SynonymsField'
import EditBar from '../EditBar/EditBar'
import uiSchema from './uiSchema'
import styles from './Edit.css'

class Edit extends Component {
  static propTypes = {
    dataDir: PropTypes.string.isRequired,
    productSchema: PropTypes.object.isRequired,
    editedProduct: PropTypes.object.isRequired,
    products: PropTypes.array.isRequired,
    nutrients: PropTypes.array.isRequired,
    faos: PropTypes.array.isRequired,
    actions: PropTypes.object.isRequired
  }

  state = {
    saveModalOpen: false,
    backModalOpen: false,
    errorModalOpen: false,
    errorMessages: []
  }

  toggleErrorModal = () => {
    this.setState({
      errorModalOpen: !this.state.errorModalOpen
    })
  }

  toggleSaveModal = () => {
    this.setState({
      saveModalOpen: !this.state.saveModalOpen
    })
  }

  toggleBackModal = () => {
    this.setState({
      backModalOpen: !this.state.backModalOpen
    })
  }

  handleBackConfirmClick = () => {
    const {
      setProductType,
      clearSearchInput,
      changeLocation
    } = this.props.actions

    setProductType('old')
    clearSearchInput()
    changeLocation('/')
    this.toggleBackModal()
  }

  handleSaveConfirmClick = () => {
    const {
      mergeEditedToProducts,
      saveAllProducts,
      saveEditedProduct
    } = this.props.actions

    mergeEditedToProducts()
    saveAllProducts()
    saveEditedProduct()
    this.toggleSaveModal()
  }

  handleError = errors => {
    const errorMessages = errors
      .map(error => {
        return (

            {error.stack.split('.')[1]}

        )
      })

    this.setState({
      errorMessages
    })

    this.toggleErrorModal()
  }

  handleSubmit = ({formData}) => {
    const { updateEditedProduct } = this.props.actions

    // the formData object passed to handleSubmit never has any
    // errors??!! This means invalid values get removed?!
    updateEditedProduct({
      editedProduct: formData
    })

    this.toggleSaveModal()
  }

  render () {
    const {
      actions,
      productSchema,
      editedProduct,
      products,
      nutrients,
      faos
    } = this.props

    const fields = {
      synonyms: SynonymsField
    }

    // the formContext object is consumed by the Oracle/Autosuggest
    const formContext = {
      allData: {
        products,
        nutrients,
        faos
      }
    }

    return (

        <EditBar
          actions={actions}
          backModalOpen={this.state.backModalOpen}
          toggleBackModal={this.toggleBackModal}
          handleBackConfirmClick={this.handleBackConfirmClick} />

          <Card>
            <CardBlock>
              <CardTitle>{editedProduct.name}</CardTitle>
              <CardSubtitle>{editedProduct.filename}</CardSubtitle>
            </CardBlock>
            <CardBlock>
              <Form
                schema={productSchema}
                uiSchema={uiSchema}
                formData={editedProduct}
                formContext={formContext}
                FieldTemplate={CustomFieldTemplate}
                ArrayFieldTemplate={CustomArrayTemplate}
                showErrorList={false}
                liveValidate
                onError={this.handleError}
                onSubmit={this.handleSubmit}
                fields={fields} >

* required field

                <CardBlock>

                    <Col xs={{ size: 'auto', offset: 8 }}>
                      <Button
                        type='button'
                        outline
                        color='warning'
                        onClick={this.toggleBackModal}>
                        Back
                      </Button>
                      <ConfirmRejectModal
                        isOpen={this.state.backModalOpen}
                        toggle={this.toggleBackModal}
                        onConfirmClick={this.handleBackConfirmClick}
                        onRejectClick={this.toggleBackModal}
                        header='Did you save your changes?'
                        body={'Changes will be lost when you go back to the table view without saving!'}
                        confirmBtnText='Back to table view'
                        rejectBtnText='Cancel' />{' '}
                      <Button
                        type='submit'
                        outline
                        color='success'>
                        Save changes
                      </Button>
                      <Modal
                        isOpen={this.state.errorModalOpen}
                        toggle={this.toggleErrorModal} >
                        <ModalHeader
                          toggle={this.toggleErrorModal} >
                          {'Cannot save product. Form contains errors:'}
                        </ModalHeader>
                        <ModalBody
                          className={styles.errorModalText} >
                          {this.state.errorMessages}
                        </ModalBody>
                        <ModalFooter>
                          <Button
                            color='success'
                            onClick={this.toggleErrorModal} >
                            Fix Errors
                          </Button>
                        </ModalFooter>
                      </Modal>
                      <ConfirmRejectModal
                        isOpen={this.state.saveModalOpen}
                        toggle={this.toggleSaveModal}
                        onConfirmClick={this.handleSaveConfirmClick}
                        onRejectClick={this.toggleSaveModal}
                        header='Saving will overwrite file!'
                        body={`Clicking save will overwrite ${editedProduct.filename}! Are you sure?`}
                        confirmBtnText='Save!'
                        rejectBtnText='Cancel' />
                    </Col>

                </CardBlock>
              </Form>
            </CardBlock>
          </Card>

    )
  }
}

export default Edit
<commit_msg>Set product to old after first save. Prevents creation of duplicate ids<commit_after>import React, { Component, PropTypes } from 'react'
import {
  Button,
  Card,
  CardBlock,
  CardTitle,
  CardSubtitle,
  Col,
  Modal,
  ModalHeader,
  ModalBody,
  ModalFooter
} from 'reactstrap'
import Form from 'react-jsonschema-form'
import ConfirmRejectModal from '../ConfirmRejectModal/ConfirmRejectModal'
import CustomFieldTemplate from './CustomFieldTemlpate'
import CustomArrayTemplate from './CustomArrayTemplate'
import SynonymsField from './SynonymsField'
import EditBar from '../EditBar/EditBar'
import uiSchema from './uiSchema'
import styles from './Edit.css'

class Edit extends Component {
  static propTypes = {
    dataDir: PropTypes.string.isRequired,
    productSchema: PropTypes.object.isRequired,
    editedProduct: PropTypes.object.isRequired,
    products: PropTypes.array.isRequired,
    nutrients: PropTypes.array.isRequired,
    faos: PropTypes.array.isRequired,
    actions: PropTypes.object.isRequired
  }

  state = {
    saveModalOpen: false,
    backModalOpen: false,
    errorModalOpen: false,
    errorMessages: []
  }

  toggleErrorModal = () => {
    this.setState({
      errorModalOpen: !this.state.errorModalOpen
    })
  }

  toggleSaveModal = () => {
    this.setState({
      saveModalOpen: !this.state.saveModalOpen
    })
  }

  toggleBackModal = () => {
    this.setState({
      backModalOpen: !this.state.backModalOpen
    })
  }

  handleBackConfirmClick = () => {
    const {
      setProductType,
      clearSearchInput,
      changeLocation
    } = this.props.actions

    setProductType('old')
    clearSearchInput()
    changeLocation('/')
    this.toggleBackModal()
  }

  handleSaveConfirmClick = () => {
    const {
      mergeEditedToProducts,
      saveAllProducts,
      saveEditedProduct,
      setProductType
    } = this.props.actions

    mergeEditedToProducts()
    saveAllProducts()
    saveEditedProduct()
    setProductType('old')
    this.toggleSaveModal()
  }

  handleError = errors => {
    const errorMessages = errors
      .map(error => {
        return (

            {error.stack.split('.')[1]}

        )
      })

    this.setState({
      errorMessages
    })

    this.toggleErrorModal()
  }

  handleSubmit = ({formData}) => {
    const { updateEditedProduct } = this.props.actions

    // the formData object passed to handleSubmit never has any
    // errors??!! This means invalid values get removed?!
    updateEditedProduct({
      editedProduct: formData
    })

    this.toggleSaveModal()
  }

  render () {
    const {
      actions,
      productSchema,
      editedProduct,
      products,
      nutrients,
      faos
    } = this.props

    const fields = {
      synonyms: SynonymsField
    }

    // the formContext object is consumed by the Oracle/Autosuggest
    const formContext = {
      allData: {
        products,
        nutrients,
        faos
      }
    }

    return (

        <EditBar
          actions={actions}
          backModalOpen={this.state.backModalOpen}
          toggleBackModal={this.toggleBackModal}
          handleBackConfirmClick={this.handleBackConfirmClick} />

          <Card>
            <CardBlock>
              <CardTitle>{editedProduct.name}</CardTitle>
              <CardSubtitle>{editedProduct.filename}</CardSubtitle>
            </CardBlock>
            <CardBlock>
              <Form
                schema={productSchema}
                uiSchema={uiSchema}
                formData={editedProduct}
                formContext={formContext}
                FieldTemplate={CustomFieldTemplate}
                ArrayFieldTemplate={CustomArrayTemplate}
                showErrorList={false}
                liveValidate
                onError={this.handleError}
                onSubmit={this.handleSubmit}
                fields={fields} >

* required field

                <CardBlock>

                    <Col xs={{ size: 'auto', offset: 8 }}>
                      <Button
                        type='button'
                        outline
                        color='warning'
                        onClick={this.toggleBackModal}>
                        Back
                      </Button>
                      <ConfirmRejectModal
                        isOpen={this.state.backModalOpen}
                        toggle={this.toggleBackModal}
                        onConfirmClick={this.handleBackConfirmClick}
                        onRejectClick={this.toggleBackModal}
                        header='Did you save your changes?'
                        body={'Changes will be lost when you go back to the table view without saving!'}
                        confirmBtnText='Back to table view'
                        rejectBtnText='Cancel' />{' '}
                      <Button
                        type='submit'
                        outline
                        color='success'>
                        Save changes
                      </Button>
                      <Modal
                        isOpen={this.state.errorModalOpen}
                        toggle={this.toggleErrorModal} >
                        <ModalHeader
                          toggle={this.toggleErrorModal} >
                          {'Cannot save product. Form contains errors:'}
                        </ModalHeader>
                        <ModalBody
                          className={styles.errorModalText} >
                          {this.state.errorMessages}
                        </ModalBody>
                        <ModalFooter>
                          <Button
                            color='success'
                            onClick={this.toggleErrorModal} >
                            Fix Errors
                          </Button>
                        </ModalFooter>
                      </Modal>
                      <ConfirmRejectModal
                        isOpen={this.state.saveModalOpen}
                        toggle={this.toggleSaveModal}
                        onConfirmClick={this.handleSaveConfirmClick}
                        onRejectClick={this.toggleSaveModal}
                        header='Saving will overwrite file!'
                        body={`Clicking save will overwrite ${editedProduct.filename}! Are you sure?`}
                        confirmBtnText='Save!'
                        rejectBtnText='Cancel' />
                    </Col>

                </CardBlock>
              </Form>
            </CardBlock>
          </Card>

    )
  }
}

export default Edit
<|endoftext|>


<commit_before>/* @flow weak */

/**
 * OfflineMobile dataTable Expansion
 * Sustainable Solutions (NZ) Ltd. 2016
 */

import React, {
  StyleSheet,
  View,
} from 'react-native';

export default function Expansion(props) {
  return (
    <View style={styles.expansion}>
      {props.children}
    </View>
  );
}

Expansion.propTypes = {
  children: React.PropTypes.any,
};

const styles = StyleSheet.create({
  expansion: {
    flex: 1,
    flexDirection: 'row',
    backgroundColor: 'magenta',
  },
});
<commit_msg>Expansion refactored<commit_after>/* @flow weak */

/**
 * OfflineMobile dataTable Expansion
 * Sustainable Solutions (NZ) Ltd. 2016
 */

import React, {
  StyleSheet,
  View,
} from 'react-native';

export default function Expansion(props) {
  const { children, style, ...viewProps } = props;
  return (
    <View {...viewProps} style={[styles.expansion, style]}>
      {children}
    </View>
  );
}

Expansion.propTypes = {
  style: React.PropTypes.number,
  children: React.PropTypes.any,
};

const styles = StyleSheet.create({
  expansion: {
    flex: 1,
    flexDirection: 'row',
  },
});
<|endoftext|>


<commit_before>// @flow
import React, { Component } from 'react';
import steem from 'steem';
import { Button, Checkbox, Divider, Form, Grid, Header, Message, Segment, Table } from 'semantic-ui-react';

export default class KeysMemo extends Component {
  constructor(props) {
    super(props);
    this.state.account = this.props.keys.confirmMemo.account;
    this.state.public = this.props.keys.confirmMemo.public;
    this.state.errors = this.validate(this.state);
  }
  state = {
    account: '',
    wif: '',
    errors: {
      wif_error_invalid: false,
      wif_not_valid: false,
    }
  }

  handleChange = (
    e: SyntheticEvent, { name, value }: { name: string, value: string }
  ) => {
    const newState = Object.assign({}, this.state, {
      [name]: value
    });
    newState.errors = this.validate(newState);
    this.setState(newState);
  }

  validate = (newState) => {
    const errors = {};
    errors.wif_error_invalid = !steem.auth.isWif(newState.wif);
    errors.wif_not_valid = true;
    if (newState.wif && steem.auth.wifIsValid(newState.wif, newState.public) === true) {
      errors.wif_not_valid = false;
    }
    return errors;
  }

  hasErrors = () => {
    const errors = this.state.errors;
    return !!Object.keys(errors).filter((error) => errors[error]).length;
  }

  handleSubmit = (e: SyntheticEvent) => {
    e.preventDefault();
    const { addMemoKey } = this.props.actions;
    const { account, wif } = this.state;
    addMemoKey(account, wif);
  }

  render() {
    const { account, wif } = this.state;
    const errors = this.state.errors;
    const hasErrors = this.hasErrors();
    const messages = {
      wif_error_invalid: 'They WIF Private Key you have is not valid.',
      wif_not_valid: 'The WIF Private Key does not match the memo key of this account.'
    };
    let handleMethodReset = false;
    if (this.props.handleMethodReset) {
      handleMethodReset = (
        <Segment basic textAlign="center">
          <Button
            size="large"
            color="red"
            content="Cancel"
            onClick={this.props.handleMethodReset}
          />
        </Segment>
      );
    }
    let message = false;
    let warning = false;
    if (hasErrors) {
      if (errors.wif_not_valid) {
        message = messages.wif_not_valid;
      }
      if (errors.wif_error_invalid) {
        message = messages.wif_error_invalid;
      }
      warning = (
        <Message
          error
          icon="warning"
          content={message}
        />
      );
    }
    let display = (
      <Segment padded>
        <Header>
          <Header.Subheader>
            If you'd like to encrypt transaction memos from this account, please add the memo private key associated to the specified account:
          </Header.Subheader>
        </Header>
        <Divider hidden />
        <Form
          error={hasErrors}
        >
          <Grid divided>
            <Grid.Row>
              <Grid.Column width={16}>
                <Form.Input
                  fluid
                  label="Account Name"
                  name="account"
                  value={this.props.keys.confirmMemo.account}
                />
                <Form.Input
                  fluid
                  label="Public Memo Key"
                  name="public"
                  value={this.props.keys.confirmMemo.public}
                />
                <Form.Input
                  label="WIF Memo Private Key"
                  type="password"
                  name="wif"
                  value={wif}
                  onChange={this.handleChange}
                />
              </Grid.Column>
            </Grid.Row>
          </Grid>
          {warning}
          <Divider />
          <Button disabled={hasErrors} fluid size="large" color="green" onClick={this.handleSubmit}>
            Add Memo Key
          </Button>
          {handleMethodReset}
        </Form>
      </Segment>
    );
    return display;
  }
}
<commit_msg>Added a warning to the memo key screen<commit_after>// @flow
import React, { Component } from 'react';
import steem from 'steem';
import { Button, Checkbox, Divider, Form, Grid, Header, Message, Segment, Table } from 'semantic-ui-react';

export default class KeysMemo extends Component {
  constructor(props) {
    super(props);
    this.state.account = this.props.keys.confirmMemo.account;
    this.state.public = this.props.keys.confirmMemo.public;
    this.state.errors = this.validate(this.state);
  }
  state = {
    account: '',
    wif: '',
    errors: {
      wif_error_invalid: false,
      wif_not_valid: false,
    }
  }

  handleChange = (
    e: SyntheticEvent, { name, value }: { name: string, value: string }
  ) => {
    const newState = Object.assign({}, this.state, {
      [name]: value
    });
    newState.errors = this.validate(newState);
    this.setState(newState);
  }

  validate = (newState) => {
    const errors = {};
    errors.wif_error_invalid = !steem.auth.isWif(newState.wif);
    errors.wif_not_valid = true;
    if (newState.wif && steem.auth.wifIsValid(newState.wif, newState.public) === true) {
      errors.wif_not_valid = false;
    }
    return errors;
  }

  hasErrors = () => {
    const errors = this.state.errors;
    return !!Object.keys(errors).filter((error) => errors[error]).length;
  }

  handleSubmit = (e: SyntheticEvent) => {
    e.preventDefault();
    const { addMemoKey } = this.props.actions;
    const { account, wif } = this.state;
    addMemoKey(account, wif);
  }

  render() {
    const { account, wif } = this.state;
    const errors = this.state.errors;
    const hasErrors = this.hasErrors();
    const messages = {
      wif_error_invalid: 'They WIF Private Key you have is not valid.',
      wif_not_valid: 'The WIF Private Key does not match the memo key of this account.'
    };
    let handleMethodReset = false;
    if (this.props.handleMethodReset) {
      handleMethodReset = (
        <Segment basic textAlign="center">
          <Button
            size="large"
            color="red"
            content="Cancel"
            onClick={this.props.handleMethodReset}
          />
        </Segment>
      );
    }
    let message = false;
    let warning = false;
    if (hasErrors) {
      if (errors.wif_not_valid) {
        message = messages.wif_not_valid;
      }
      if (errors.wif_error_invalid) {
        message = messages.wif_error_invalid;
      }
      warning = (
        <Message
          error
          icon="warning"
          content={message}
        />
      );
    }
    let display = (
      <Segment padded>
        <Message
          error
          icon="warning"
          content="This feature is intended for advanced users only, please make sure you know what you're doing while encrypting memos. Using this feature incorrectly may cause the loss of funds deposited to an exchange."
        />
        <Header>
          <Header.Subheader>
            If you'd like to encrypt transaction memos from this account, please add the memo private key associated to the specified account:
          </Header.Subheader>
        </Header>
        <Divider hidden />
        <Form
          error={hasErrors}
        >
          <Grid divided>
            <Grid.Row>
              <Grid.Column width={16}>
                <Form.Input
                  fluid
                  label="Account Name"
                  name="account"
                  value={this.props.keys.confirmMemo.account}
                />
                <Form.Input
                  fluid
                  label="Public Memo Key"
                  name="public"
                  value={this.props.keys.confirmMemo.public}
                />
                <Form.Input
                  label="WIF Memo Private Key"
                  type="password"
                  name="wif"
                  value={wif}
                  onChange={this.handleChange}
                />
              </Grid.Column>
            </Grid.Row>
          </Grid>
          {warning}
          <Divider />
          <Button disabled={hasErrors} fluid size="large" color="green" onClick={this.handleSubmit}>
            Add Memo Key
          </Button>
          {handleMethodReset}
        </Form>
      </Segment>
    );
    return display;
  }
}
<|endoftext|>


<commit_before>var Reversi = Reversi || {};

Reversi.Player1 = "Player 1"
Reversi.Player2 = "Player 2";
Reversi.Empty = "Empty";

Reversi.Board = function() {
    var _board = [];

    var _directions = [
        new Reversi.Direction(1, 0),
        new Reversi.Direction(0, 1),
        new Reversi.Direction(-1, 0),
        new Reversi.Direction(0, -1),
        new Reversi.Direction(-1, -1),
        new Reversi.Direction(1, 1),
        new Reversi.Direction(-1, 1),
        new Reversi.Direction(1, -1)
    ];

    setupBoard();

    function setupBoard() {
        for (var i = 0; i < 8; i++) {
            _board[i] = new Array(8);

            for (var j = 0; j < 8; j++) {
                _board[i][j] = Reversi.Empty;
            }
        }

        _board[3][4] = Reversi.Player1;
        _board[4][3] = Reversi.Player1;

        _board[3][3] = Reversi.Player2;
        _board[4][4] = Reversi.Player2;
    }

    var makeMove = function(i, j, color) {
        var success = false;
        if (_board[i][j] === Reversi.Empty) {
            var oppositeColor = color === Reversi.Player1
                ? Reversi.Player2
                : Reversi.Player1;

            for (var d = 0; d < _directions.length; d++) {
                var next = _directions[d].getNext(i, j);

                if (next !== false && _board[next.row][next.col] === oppositeColor && lineContainsColor(i, j, color, _directions[d])) {
                    _board[i][j] = color;
                    success = true;
                    while (next !== false && _board[next.row][next.col] !== color) {
                        _board[next.row][next.col] = color;
                        next = _directions[d].getNext(next.row, next.col);
                    }
                }
            }
        }

        return success;
    };

    var canMakeMove = function(i, j, color) {
        if (_board[i][j] === Reversi.Empty) {
            var oppositeColor = color === Reversi.Player1
                ? Reversi.Player2
                : Reversi.Player1;

            for (var d = 0; d < _directions.length; d++) {
                var next = _directions[d].getNext(i, j);

                if (next !== false && _board[next.row][next.col] === oppositeColor && lineContainsColor(i, j, color, _directions[d])) {
                    return true;
                }
            }
        }

        return false;
    };

    var lineContainsColor = function(i, j, color, direction) {
        var next = direction.getNext(i, j);

        if (next === false || _board[next.row][next.col] === Reversi.Empty) {
            return false;
        }

        if (_board[next.row][next.col] === color) {
            return true;
        }

        return lineContainsColor(next.row, next.col, color);
    };

    var getStatus = function(i, j) {
        return _board[i][j];
    };

    var set =  function (i, j, color) {
        _board[i][j] = color;
    };

    return {
        getStatus: getStatus,
        canMakeMove: canMakeMove,
        makeMove: makeMove,
        set: set //for testing
    };
};<commit_msg>fixed tests. Pulled out some duplication by extracting 2 functions.<commit_after>var Reversi = Reversi || {};

Reversi.Player1 = "Player 1"
Reversi.Player2 = "Player 2";
Reversi.Empty = "Empty";

Reversi.Board = function() {
    var _board = [];

    var _directions = [
        new Reversi.Direction(1, 0),
        new Reversi.Direction(0, 1),
        new Reversi.Direction(-1, 0),
        new Reversi.Direction(0, -1),
        new Reversi.Direction(-1, -1),
        new Reversi.Direction(1, 1),
        new Reversi.Direction(-1, 1),
        new Reversi.Direction(1, -1)
    ];

    setupBoard();

    function setupBoard() {
        for (var i = 0; i < 8; i++) {
            _board[i] = new Array(8);

            for (var j = 0; j < 8; j++) {
                _board[i][j] = Reversi.Empty;
            }
        }

        _board[3][4] = Reversi.Player1;
        _board[4][3] = Reversi.Player1;

        _board[3][3] = Reversi.Player2;
        _board[4][4] = Reversi.Player2;
    }

    var makeMove = function(i, j, color) {
        var success = false;

        if (_board[i][j] === Reversi.Empty) {
            for (var d = 0; d < _directions.length; d++) {
                if (surroundsOppositePlayer(i, j, color, _directions[d])) {
                    colorCapturedCells(i, j, color, _directions[d]);

                    success = true;
                }
            }
        }

        return success;
    };

    var colorCapturedCells = function(i, j, color, direction) {
        _board[i][j] = color;

        var next = direction.getNext(i, j);

        while (next !== false && _board[next.row][next.col] !== color) {
            _board[next.row][next.col] = color;
            next = direction.getNext(next.row, next.col);
        }
    };

    var canMakeMove = function(i, j, color) {
        if (_board[i][j] === Reversi.Empty) {
            for (var d = 0; d < _directions.length; d++) {
                if (surroundsOppositePlayer(i, j, color, _directions[d])) {
                    return true;
                }
            }
        }

        return false;
    };

    var surroundsOppositePlayer = function(i, j, color, direction) {
        var oppositeColor = color === Reversi.Player1
            ? Reversi.Player2
            : Reversi.Player1;

        var next = direction.getNext(i, j);

        return next !== false && _board[next.row][next.col] === oppositeColor && lineContainsColor(i, j, color, direction)
    };

    var lineContainsColor = function(i, j, color, direction) {
        var next = direction.getNext(i, j);

        if (next === false || _board[next.row][next.col] === Reversi.Empty) {
            return false;
        }

        if (_board[next.row][next.col] === color) {
            return true;
        }

        return lineContainsColor(next.row, next.col, color, direction);
    };

    var getStatus = function(i, j) {
        return _board[i][j];
    };

    var set =  function (i, j, color) {
        _board[i][j] = color;
    };

    return {
        getStatus: getStatus,
        canMakeMove: canMakeMove,
        makeMove: makeMove,
        set: set //for testing
    };
};<|endoftext|>


<commit_before>import React, { Component } from 'react'
import { View } from 'react-native'

import {DefaultText} from '../components/textComponents'
import VehicleImage from '../components/VehicleImage'

import styles from '../styles/stylesheet'
import strings from '../resources/translations'

export default class RouteInfo extends Component {
    render()
    {
        return (<View style={styles.stopRequestStyle}>
          <View style={styles.stopRequestVehicleInfo}>
            <VehicleImage style={styles.busIcon} vehicleType={this.props.vehicleType}/>
            <View style={styles.vehicleInfoWrapper}>
              <DefaultText style={styles.vehicleInfo}>{this.props.vehicleLine} {this.props.vehicleDestination}</DefaultText>
            </View>
          </View>
          <View style={styles.vehicleArrivesInWrapper}>
            <DefaultText style={styles.vehicleArrivesInText}>{strings.vehicleArrivesIn}</DefaultText>
          </View>
          <View>
            <DefaultText style={styles.vehicleMinutesLeft}>{this.props.vehicleMinutesLeft}</DefaultText>
          </View>
        </View>)
    }
}

RouteInfo.propTypes = {
    vehicleType: React.PropTypes.number.isRequired,
    vehicleLine: React.PropTypes.string.isRequired,
    vehicleDestination: React.PropTypes.string.isRequired,
    vehicleMinutesLeft: React.PropTypes.string.isRequired
}

export class RouteInfoForStop extends Component {
    render()
    {
        return (<View style={styles.stopRequestStyle}>
          <View style={styles.vehicleArrivesInWrapper}>
            <DefaultText style={styles.vehicleArrivesInText}>{strings.arrives} {this.props.stopName} ({this.props.stopCode}) {strings.in}:</DefaultText>
          </View>
          <View>
            <DefaultText style={styles.vehicleMinutesLeft}>{this.props.vehicleMinutesLeft}</DefaultText>
          </View>
        </View>)
    }
}

RouteInfoForStop.propTypes = {
    stopName: React.PropTypes.string.isRequired,
    stopCode: React.PropTypes.string.isRequired,
    vehicleMinutesLeft: React.PropTypes.string.isRequired
}
<commit_msg>change text when props.mode is 'stop' or 'arrive'<commit_after>import React, { Component } from 'react'
import { View } from 'react-native'

import {DefaultText} from '../components/textComponents'
import VehicleImage from '../components/VehicleImage'

import styles from '../styles/stylesheet'
import strings from '../resources/translations'

export default class RouteInfo extends Component {
    render()
    {
        return (<View style={styles.stopRequestStyle}>
          <View style={styles.stopRequestVehicleInfo}>
            <VehicleImage style={styles.busIcon} vehicleType={this.props.vehicleType}/>
            <View style={styles.vehicleInfoWrapper}>
              <DefaultText style={styles.vehicleInfo}>{this.props.vehicleLine} {this.props.vehicleDestination}</DefaultText>
            </View>
          </View>
          <View style={styles.vehicleArrivesInWrapper}>
            <DefaultText style={styles.vehicleArrivesInText}>{this.props.mode == 'stop' ? strings.vehicleStopsIn : strings.vehicleArrivesIn}</DefaultText>
          </View>
          <View>
            <DefaultText style={styles.vehicleMinutesLeft}>{this.props.vehicleMinutesLeft}</DefaultText>
          </View>
        </View>)
    }
}

RouteInfo.propTypes = {
    vehicleType: React.PropTypes.number.isRequired,
    vehicleLine: React.PropTypes.string.isRequired,
    vehicleDestination: React.PropTypes.string.isRequired,
    vehicleMinutesLeft: React.PropTypes.string.isRequired
}

export class RouteInfoForStop extends Component {
    render()
    {
        return (<View style={styles.stopRequestStyle}>
          <View style={styles.vehicleArrivesInWrapper}>
            <DefaultText style={styles.vehicleArrivesInText}>{strings.arrives} {this.props.stopName} ({this.props.stopCode}) {strings.in}:</DefaultText>
          </View>
          <View>
            <DefaultText style={styles.vehicleMinutesLeft}>{this.props.vehicleMinutesLeft}</DefaultText>
          </View>
        </View>)
    }
}

RouteInfoForStop.propTypes = {
    stopName: React.PropTypes.string.isRequired,
    stopCode: React.PropTypes.string.isRequired,
    vehicleMinutesLeft: React.PropTypes.string.isRequired
}
<|endoftext|>


<commit_before>(function () {
    'use strict';

    var website = openerp.website;
    website.templates.push('/website/static/src/xml/website.translator.xml');
    var nodialog = 'website_translator_nodialog';

    website.EditorBar.include({
        start: function () {
            var self = this;
            return this._super.apply(this, arguments).then(function () {
                // TODO: refactor once xmo's feature branch is merged
                //       and make t-field's work as well as text translation
                self.$('button[data-action=edit]').text("Translate");
                self.$('[data-action=snippet]').hide();
                self.$('#customize-menu-button').hide();
            });
        },
        edit: function () {
            var self = this;
            if (!localStorage[nodialog]) {
                var dialog = new website.TranslatorDialog();
                dialog.appendTo($(document.body));
                dialog.on('activate', this, function () {
                    localStorage[nodialog] = dialog.$('input[name=do_not_show]').prop('checked') || '';
                    dialog.$el.modal('hide');
                    this.translate();
                });
            } else {
                this.translate();
            }
        },
        translate: function () {
            var self = this;
            // this.edit();
            this.$('#website-top-view').hide();
            this.$('#website-top-edit').show();
            $('.css_non_editable_mode_hidden').removeClass("css_non_editable_mode_hidden");

            this.translations = null;
            openerp.jsonRpc('/website/get_view_translations', 'call', {
                'xml_id': $(document.documentElement).data('view-xmlid'),
                'lang': website.get_context().lang,
            }).then(function (translations) {
                self.translations = translations;
                self.processTranslatableNodes();
            });
        },
        processTranslatableNodes: function () {
            var self = this;
            var $editables = $('[data-oe-model="ir.ui.view"]')
                    .not('link, script')
                    .not('.oe_snippets,.oe_snippet, .oe_snippet *')
                    .not('[data-oe-type]');

            $editables.each(function () {
                self.transNode(this, $(this).attr('data-oe-id')|0);
            });
            $('.oe_translatable_text').prop('contenteditable', true).on('paste', function () {
                var node = $(this);
                setTimeout(function () {
                    self.sanitizeNode(node);
                }, 0);
            });
            $(document).on('blur keyup paste', '.oe_translatable_text[contenteditable]', function(ev) {
                var $node = $(this);
                setTimeout(function () {
                    // Doing stuff next tick because paste and keyup events are
                    // fired before the content is changed
                    if (ev.type == 'paste') {
                        self.sanitizeNode($node[0]);
                    }
                    if ($node.data('initial_content') !== $node.text()) {
                        $node.addClass('oe_dirty').removeClass('oe_translatable_todo');
                    }
                }, 0);
            });
        },
        sanitizeNode: function (node) {
            node.text(node.text());
        },
        isTextNode: function (node) {
            return node.nodeType === 3 || node.nodeType === 4;
        },
        isTranslatable: function (text) {
            return text && _.str.trim(text) !== '';
        },
        markTranslatableNode: function (node, view_id) {
            // TODO: link nodes with same content
            node.className += ' oe_translatable_text';
            node.setAttribute('data-oe-translation-view-id', view_id);
            var content = node.childNodes[0].data.trim();
            var trans = this.translations.filter(function (t) {
                return t.res_id === view_id && t.value === content;
            });
            if (trans.length) {
                node.setAttribute('data-oe-translation-id', trans[0].id);
            } else {
                node.className += ' oe_translatable_todo';
            }
            node.contentEditable = true;
            $(node).data('initial_content', content);
        },
        save: function () {
            var trans = {};
            // this._super.apply(this, arguments);
            $('.oe_translatable_text.oe_dirty').each(function () {
                var $node = $(this);
                var data = $node.data();
                if (!trans[data.oeTranslationViewId]) {
                    trans[data.oeTranslationViewId] = [];
                }
                trans[data.oeTranslationViewId].push({
                    initial_content: data.initial_content,
                    new_content: $node.text(),
                    translation_id: data.oeTranslationId || null
                });
            });
            openerp.jsonRpc('/website/set_translations', 'call', {
                'data': trans,
                'lang': website.get_context()['lang'],
            }).then(function () {
                website.reload();
            }).fail(function () {
                // TODO: bootstrap alert with error message
                alert("Could not save translation");
            });
        },
        transNode: function (node, view_id) {
            if (node.childNodes.length === 1
                    && this.isTextNode(node.childNodes[0])
                    && !node.getAttribute('data-oe-model')) {
                this.markTranslatableNode(node, view_id);
            } else {
                for (var i = 0, l = node.childNodes.length; i < l; i ++) {
                    var n = node.childNodes[i];
                    if (this.isTextNode(n)) {
                        if (this.isTranslatable(n.data)) {
                            var container = document.createElement('span');
                            node.insertBefore(container, n);
                            container.appendChild(n);
                            this.markTranslatableNode(container, view_id);
                        }
                    } else {
                        this.transNode(n, view_id);
                    }
                }
            }
        },
    });

    website.TranslatorDialog = openerp.Widget.extend({
        events: _.extend({}, website.EditorBar.prototype.events, {
            'hidden.bs.modal': 'destroy',
            'click button[data-action=activate]': function (ev) {
                this.trigger('activate');
            },
        }),
        template: 'website.TranslatorDialog',
        start: function () {
            this.$el.modal();
        },
    });
})();
<commit_msg>[FIX] Use sourc-id in client translator<commit_after>(function () {
    'use strict';

    var website = openerp.website;
    website.templates.push('/website/static/src/xml/website.translator.xml');
    var nodialog = 'website_translator_nodialog';

    website.EditorBar.include({
        start: function () {
            var self = this;
            return this._super.apply(this, arguments).then(function () {
                // TODO: refactor once xmo's feature branch is merged
                //       and make t-field's work as well as text translation
                self.$('button[data-action=edit]').text("Translate");
                self.$('[data-action=snippet]').hide();
                self.$('#customize-menu-button').hide();
            });
        },
        edit: function () {
            var self = this;
            if (!localStorage[nodialog]) {
                var dialog = new website.TranslatorDialog();
                dialog.appendTo($(document.body));
                dialog.on('activate', this, function () {
                    localStorage[nodialog] = dialog.$('input[name=do_not_show]').prop('checked') || '';
                    dialog.$el.modal('hide');
                    this.translate();
                });
            } else {
                this.translate();
            }
        },
        translate: function () {
            var self = this;
            // this.edit();
            this.$('#website-top-view').hide();
            this.$('#website-top-edit').show();
            $('.css_non_editable_mode_hidden').removeClass("css_non_editable_mode_hidden");

            this.translations = null;
            openerp.jsonRpc('/website/get_view_translations', 'call', {
                'xml_id': $(document.documentElement).data('view-xmlid'),
                'lang': website.get_context().lang,
            }).then(function (translations) {
                self.translations = translations;
                self.processTranslatableNodes();
            });
        },
        processTranslatableNodes: function () {
            var self = this;
            var $editables = $('[data-oe-model="ir.ui.view"]')
                    .not('link, script')
                    .not('.oe_snippets,.oe_snippet, .oe_snippet *')
                    .not('[data-oe-type]');

            $editables.each(function () {
                var view_id = $(this).attr('data-oe-source-id') || $(this).attr('data-oe-id');
                self.transNode(this, view_id|0);
            });
            $('.oe_translatable_text').prop('contenteditable', true).on('paste', function () {
                var node = $(this);
                setTimeout(function () {
                    self.sanitizeNode(node);
                }, 0);
            });
            $(document).on('blur keyup paste', '.oe_translatable_text[contenteditable]', function(ev) {
                var $node = $(this);
                setTimeout(function () {
                    // Doing stuff next tick because paste and keyup events are
                    // fired before the content is changed
                    if (ev.type == 'paste') {
                        self.sanitizeNode($node[0]);
                    }
                    if ($node.data('initial_content') !== $node.text()) {
                        $node.addClass('oe_dirty').removeClass('oe_translatable_todo');
                    }
                }, 0);
            });
        },
        sanitizeNode: function (node) {
            node.text(node.text());
        },
        isTextNode: function (node) {
            return node.nodeType === 3 || node.nodeType === 4;
        },
        isTranslatable: function (text) {
            return text && _.str.trim(text) !== '';
        },
        markTranslatableNode: function (node, view_id) {
            // TODO: link nodes with same content
            node.className += ' oe_translatable_text';
            node.setAttribute('data-oe-translation-view-id', view_id);
            var content = node.childNodes[0].data.trim();
            var trans = this.translations.filter(function (t) {
                return t.res_id === view_id && t.value === content;
            });
            if (trans.length) {
                node.setAttribute('data-oe-translation-id', trans[0].id);
            } else {
                node.className += ' oe_translatable_todo';
            }
            node.contentEditable = true;
            $(node).data('initial_content', content);
        },
        save: function () {
            var trans = {};
            // this._super.apply(this, arguments);
            $('.oe_translatable_text.oe_dirty').each(function () {
                var $node = $(this);
                var data = $node.data();
                if (!trans[data.oeTranslationViewId]) {
                    trans[data.oeTranslationViewId] = [];
                }
                trans[data.oeTranslationViewId].push({
                    initial_content: data.initial_content,
                    new_content: $node.text(),
                    translation_id: data.oeTranslationId || null
                });
            });
            openerp.jsonRpc('/website/set_translations', 'call', {
                'data': trans,
                'lang': website.get_context()['lang'],
            }).then(function () {
                website.reload();
            }).fail(function () {
                // TODO: bootstrap alert with error message
                alert("Could not save translation");
            });
        },
        transNode: function (node, view_id) {
            if (node.childNodes.length === 1
                    && this.isTextNode(node.childNodes[0])
                    && !node.getAttribute('data-oe-model')) {
                this.markTranslatableNode(node, view_id);
            } else {
                for (var i = 0, l = node.childNodes.length; i < l; i ++) {
                    var n = node.childNodes[i];
                    if (this.isTextNode(n)) {
                        if (this.isTranslatable(n.data)) {
                            var container = document.createElement('span');
                            node.insertBefore(container, n);
                            container.appendChild(n);
                            this.markTranslatableNode(container, view_id);
                        }
                    } else {
                        this.transNode(n, view_id);
                    }
                }
            }
        },
    });

    website.TranslatorDialog = openerp.Widget.extend({
        events: _.extend({}, website.EditorBar.prototype.events, {
            'hidden.bs.modal': 'destroy',
            'click button[data-action=activate]': function (ev) {
                this.trigger('activate');
            },
        }),
        template: 'website.TranslatorDialog',
        start: function () {
            this.$el.modal();
        },
    });
})();
<|endoftext|>


<commit_before>import React, {Component} from 'react';
import { View, Text, TextInput } from 'react-native';

export default class SearchBar extends Component {
  constructor(props) {
    super(props)
    this.state = {
      text: ''
    }
  }

  //-----------------------------------
  // RENDERING
  //-----------------------------------

  render() {
    return (
      <View style={{alignSelf: 'stretch', paddingHorizontal: 20}}>
        <TextInput
            style={{height: 40, borderColor: 'gray', borderWidth: 1}}
            onKeyPress={this._handleKeyPress.bind(this)}
            onChangeText={(moreText) => this.setState({text: moreText})}
            placeholder="Search a user..."
        />
      </View>
    );
  }

  //-----------------------------------
  // PRIVATE METHODS
  //-----------------------------------

  _handleKeyPress(event) {
    if (event.nativeEvent.key === 'Enter') {
      this._update(this.state.text)
    }
  }

  _update(text) {
    this.props.onUpdate(text)
  }
}
<commit_msg>Added sane defaults to search text input<commit_after>import React, {Component} from 'react';
import { View, Text, TextInput } from 'react-native';

export default class SearchBar extends Component {
  constructor(props) {
    super(props)
    this.state = {
      text: ''
    }
  }

  //-----------------------------------
  // RENDERING
  //-----------------------------------

  render() {
    return (
      <View style={{alignSelf: 'stretch', paddingHorizontal: 20}}>
        <TextInput
            style={{height: 40, borderColor: 'gray', borderWidth: 1}}
            onKeyPress={this._handleKeyPress.bind(this)}
            onChangeText={(moreText) => this.setState({text: moreText})}
            placeholder="Search for a user..."
            autoCapitalize="words"
            auotCorrect="false"
        />
      </View>
    );
  }

  //-----------------------------------
  // PRIVATE METHODS
  //-----------------------------------

  _handleKeyPress(event) {
    if (event.nativeEvent.key === 'Enter') {
      this._update(this.state.text)
    }
  }

  _update(text) {
    this.props.onUpdate(text)
  }
}
<|endoftext|>


<commit_before>var sanitizeHtml = require('sanitize-html');

var htmlSanitizer = {};

htmlSanitizer.hookConverter = function(converter) {
  converter.hooks.chain('postConversion', sanitizeHtmlCustomized);
}

function sanitizeHtmlCustomized(state) {
  var html = state.text;
  var options = {
    allowedTags: [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol', 'nl', 'li', 'b', 'i',
                   'strong', 'em', 'strike', 'code', 'hr', 'br', 'div', 'table', 'thead', 'caption', 'tbody', 'tr',
                   'th', 'td', 'pre', 'img', 'style', 'iframe', 'del' , 'dl', 'dt', 'dd', 'defs' ],
    allowedAttributes: {
      a: [ 'href', 'name', 'target', 'data-widget-id' ],
      // We don't currently allow img itself by default, but this
      // would make sense if we did
      div: ['align', 'data-href', 'data-layout', 'data-action', 'data-show-faces', 'data-share', 'data-width', 'data-height'],
      img: [ 'src' , 'width', 'height', 'style'],
      iframe: ['width', 'height', 'src', 'frameborder', 'allowfullscreen'],
      code: ['class'],
      td: ['align', 'style'],
      th: ['align', 'style']
    },
    // Lots of these won't come up by default because we don't allow them
    selfClosing: [ 'img', 'br', 'hr', 'area', 'base', 'basefont', 'input', 'link', 'meta' ],
    // URL schemes we permit
    allowedSchemes: [ 'http', 'https', 'ftp', 'mailto' ],
    // Be more specific about allowed schemes
    // for a certain tag
    allowedSchemesByTag: {
      img: [ 'http', 'https' ]
    },
    allowedClasses: {
      a: [ 'twitter-timeline', 'twitter-share-button' ],
      div: [ 'fb-follow', 'fb-like' ],
      img: [ 'centered' ],
      svg: [ 'centered' ],
      iframe: [ 'centered' ]
    }
  };
  var svg = {'svg': [ 'width', 'height', 'style', 'viewbox', 'xmlns', 'xmlns:xlink', 'version' ],
             'circle': [ 'r', 'cx', 'cy', 'style' ],
             'ellipse': [ 'rx', 'ry', 'cx', 'cy', 'style' ],
             'polygon': [ 'points', 'style' ],
             'polyline': [ 'points', 'style' ],
             'text': [ 'x', 'y', 'dx', 'dy', 'style' ],
             'path': [ 'd' ,'style' ],
             'g': [ 'transform' ,'style', 'id' ],
             'use': [ 'xlink:href' ],
             'line': [ 'x1', 'y1', 'x2', 'y2', 'style' ],
             'rect': [ 'x', 'y', 'width', 'height', 'style' ],
             'pattern': [ 'id', 'style', 'width', 'height', 'patternunits' ]};
  options.allowedTags = options.allowedTags.concat(Object.keys(svg));
  for (var key in svg) options.allowedAttributes[key] = svg[key];
  state.text = sanitizeHtml(html, options)
  return state;
}

module.exports = htmlSanitizer;
<commit_msg>allow svg markers<commit_after>var sanitizeHtml = require('sanitize-html');

var htmlSanitizer = {};

htmlSanitizer.hookConverter = function(converter) {
  converter.hooks.chain('postConversion', sanitizeHtmlCustomized);
}

function sanitizeHtmlCustomized(state) {
  var html = state.text;
  var options = {
    allowedTags: [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol', 'nl', 'li', 'b', 'i',
                   'strong', 'em', 'strike', 'code', 'hr', 'br', 'div', 'table', 'thead', 'caption', 'tbody', 'tr',
                   'th', 'td', 'pre', 'img', 'style', 'iframe', 'del' , 'dl', 'dt', 'dd', 'defs' ],
    allowedAttributes: {
      a: [ 'href', 'name', 'target', 'data-widget-id' ],
      // We don't currently allow img itself by default, but this
      // would make sense if we did
      div: ['align', 'data-href', 'data-layout', 'data-action', 'data-show-faces', 'data-share', 'data-width', 'data-height'],
      img: [ 'src' , 'width', 'height', 'style'],
      iframe: ['width', 'height', 'src', 'frameborder', 'allowfullscreen'],
      code: ['class'],
      td: ['align', 'style'],
      th: ['align', 'style']
    },
    // Lots of these won't come up by default because we don't allow them
    selfClosing: [ 'img', 'br', 'hr', 'area', 'base', 'basefont', 'input', 'link', 'meta' ],
    // URL schemes we permit
    allowedSchemes: [ 'http', 'https', 'ftp', 'mailto' ],
    // Be more specific about allowed schemes
    // for a certain tag
    allowedSchemesByTag: {
      img: [ 'http', 'https' ]
    },
    allowedClasses: {
      a: [ 'twitter-timeline', 'twitter-share-button' ],
      div: [ 'fb-follow', 'fb-like' ],
      img: [ 'centered' ],
      svg: [ 'centered' ],
      iframe: [ 'centered' ]
    }
  };
  var svg = {'svg': [ 'width', 'height', 'style', 'viewbox', 'xmlns', 'xmlns:xlink', 'version' ],
             'circle': [ 'r', 'cx', 'cy', 'style' ],
             'ellipse': [ 'rx', 'ry', 'cx', 'cy', 'style' ],
             'polygon': [ 'points', 'style' ],
             'polyline': [ 'points', 'style' ],
             'text': [ 'x', 'y', 'dx', 'dy', 'style' ],
             'path': [ 'd' ,'style' ],
             'g': [ 'transform' ,'style', 'id' ],
             'use': [ 'xlink:href' ],
             'line': [ 'x1', 'y1', 'x2', 'y2', 'style' ],
             'rect': [ 'x', 'y', 'width', 'height', 'style' ],
             'pattern': [ 'id', 'style', 'width', 'height', 'patternunits' ],
             'marker': [ 'id', 'viewbox', 'refx', 'refy', 'markerunits', 'markerwidth', 'markerheight', 'orient' ]};
  options.allowedTags = options.allowedTags.concat(Object.keys(svg));
  for (var key in svg) options.allowedAttributes[key] = svg[key];
  state.text = sanitizeHtml(html, options)
  return state;
}

module.exports = htmlSanitizer;
<|endoftext|>


<commit_before>(function () {
  var moveContainer = '.move-container';
  var formulaContainer = '.formula-container';
  var compensationAmount = moveContainer + ' .compensation .amount';
  var defaultValue = '--';

  var CONTRACT_TYPE_EMPLOYEE = 'employee_factor';
  var CONTRACT_TYPE_CONTRACTOR = 'contractor_factor';

  // Dropdown Core functionality

  var setDropdown = function(e) {
    var $selected = $(e.currentTarget);

    if ($selected.hasClass('filter-container')) {
      return;
    }

    var key = $selected.find('.key').text();
    var value = $selected.find('.value').text();
    var displayValue = $selected.find('.display-value').text();
    var $title = $selected.parents('.dropdown').find('.title');
    var $subtitle = $selected.parents('.dropdown').find('.subtitle');

    $title.text(key);
    $title.data('selected', value || key);

    if (value) {
      $subtitle.text(value);
    } else if (displayValue) {
      $subtitle.text(displayValue);
    }
  }

  var isFullyVisible = function(el, parentEl) {
    var parentHeight = parentEl.height() - el.height(); // Make sure last item is not partially visible
    var elTop = el.position().top;
    var elBottom = elTop + (el.height());

    return (elBottom > 0 && elTop < parentHeight);
  }

  this.MoveCalculator = (function() {
    function MoveCalculator() {
      this.bindEvents();
    }

    MoveCalculator.prototype.bindEvents = function() {
      var $currentCountryDropdownContainer = $(moveContainer + ' .current-country');
      var $currentCountryDropdown = $currentCountryDropdownContainer.find('li:not(.filter-container)');
      var $currentCountryFilter = $currentCountryDropdownContainer.find('.js-country-filter');
      var $newCountryDropdownContainer = $(moveContainer + ' .new-country');
      var $newCountryDropdown = $newCountryDropdownContainer.find('li:not(.filter-container)');
      var $newCountryFilter = $newCountryDropdownContainer.find('.js-country-filter');

      var $currentCityDropdownContainer = $(moveContainer + ' .current-city');
      var $currentCityDropdown = $currentCityDropdownContainer.find('li:not(.filter-container)');
      var $currentCityFilter = $currentCityDropdownContainer.find('.js-city-filter');
      var $newCityDropdownContainer = $(moveContainer + ' .new-city');
      var $newCityDropdown = $newCityDropdownContainer.find('li:not(.filter-container)');
      var $newCityFilter = $newCityDropdownContainer.find('.js-city-filter');

      var $currentSalary = $(moveContainer + ' .current-salary input');

      // Set selected dropdown value
      $currentCountryDropdown.on('click', setDropdown);
      $currentCityDropdown.on('click', setDropdown);
      $newCountryDropdown.on('click', setDropdown);
      $newCityDropdown.on('click', setDropdown);

      // Render compensation
      $currentSalary.on('keyup', this.render.bind(this));
      $currentCountryDropdown.on('click', this.render.bind(this));
      $currentCityDropdown.on('click', this.render.bind(this));
      $newCountryDropdown.on('click', this.render.bind(this));
      $newCityDropdown.on('click', this.render.bind(this));

      // Unlock and filter city dropdown
      $currentCountryDropdown.on('click', this.filterCityDropdown.bind(this));
      $currentCountryDropdown.on('click', this.resetCityDropdown.bind(this));
      $newCountryDropdown.on('click', this.filterCityDropdown.bind(this));
      $newCountryDropdown.on('click', this.resetCityDropdown.bind(this));

      // Highlighting for Filter
      $currentCountryDropdownContainer.on('keydown', this.highlightDropdownItem.bind(this));
      $currentCityDropdownContainer.on('keydown', this.highlightDropdownItem.bind(this));
      $newCountryDropdownContainer.on('keydown', this.highlightDropdownItem.bind(this));
      $newCityDropdownContainer.on('keydown', this.highlightDropdownItem.bind(this));

      // Filtering
      $currentCountryFilter.on('keyup', this.search.bind(null, {
        dropdown: 'current-country'
      }));
      $newCountryFilter.on('keyup', this.search.bind(null, {
        dropdown: 'new-country'
      }));

      $currentCityFilter.on('keyup', this.search.bind(this, {
        dropdown: 'current-city',
        filter: 'current-country',
      }));
      $newCityFilter.on('keyup', this.search.bind(this, {
        dropdown: 'new-city',
        filter: 'new-country',
      }));

      $currentSalary.on('keypress', this.preventLetters.bind(this));

      $currentCountryDropdownContainer.on('shown.bs.dropdown', this.focusInput);
      $newCountryDropdownContainer.on('shown.bs.dropdown', this.focusInput);
      $currentCityDropdownContainer.on('shown.bs.dropdown', this.focusInput);
      $newCityDropdownContainer.on('shown.bs.dropdown', this.focusInput);
    }

    MoveCalculator.prototype.focusInput = function(e) {
      $(e.target).find('.filter-input').focus();
    }

    MoveCalculator.prototype.search = function(data, e) {
      var text = e.target.value.toLowerCase();
      var filterValue = null;
      var $items = $(moveContainer + ' .' + data.dropdown + ' li:not(.filter-container)');
      var filterName;

      if (data.filter) {
        filterName = data.filter === 'current-country' ? 'currentCountry' : 'newCountry';
        filterValue = this.getElementValues()[filterName];
      }

      $items.each(function(index, element) {
        var $element = $(element);
        var value = $element.children('.key').text().toLowerCase();
        var searchableElement = true;

        if (data.filter && filterValue) {
          searchableElement = $element.data(data.filter.toLowerCase()) === filterValue;
        }

        if (value.indexOf(text) == -1 || !searchableElement) {
          $element.addClass('hidden');
        } else {
          $element.removeClass('hidden');
        }
      });
    }

    MoveCalculator.prototype.highlightDropdownItem = function(e) {
      var $this = $(e.currentTarget);
      var listContainer = $this.find('.dropdown-menu.dropdown-scroll');
      var list = $this.find('li:not(.hidden):not(.filter-container)');
      var nextLi, prevLi, focusedLi;

      if ($this.hasClass('open')) {
        if ((e.keyCode === 38 || e.keyCode === 40) &&
            !list.filter('li.is-focused').length) {
          list.first().addClass('is-focused');
          return;
        }

        if (e.keyCode === 38 && !list.first().hasClass('is-focused')) { // Up
          prevLi = list
                    .filter('li.is-focused')
                    .prevAll('li:not(.hidden)').first();

          list.removeClass('is-focused');

          if (prevLi[0] === list.first()[0]) {
            list.first().addClass('is-focused');
          } else {
            prevLi.addClass('is-focused');
          }
        } else if (e.keyCode === 40 && !list.last().hasClass('is-focused')) { // Down
          nextLi = list
                    .filter('li.is-focused')
                    .nextAll('li:not(.hidden)').first();

          list.removeClass('is-focused');

          if (nextLi[0] === list.last()[0]) {
            list.last().addClass('is-focused');
          } else {
            nextLi.addClass('is-focused');
          }
        } else if (e.keyCode === 9) { // Tab keyCode
          $this.removeClass('open');
        } else if (e.keyCode === 13) { // Enter / Return
          // Triggering click directly while keydown is active
          // doesn't hide dropdown, this workaround does. Need better way :/
          setTimeout(function() { list.filter('li.is-focused').trigger('click'); });
        }

        focusedLi = list.filter('li.is-focused');
        if (focusedLi.length && list.first()[0] !== focusedLi[0]) {
          if (!isFullyVisible(focusedLi, listContainer)) {
            listContainer.scrollTop(list.index(focusedLi) * focusedLi.outerHeight());
          }
        }
        else {
          listContainer.scrollTop(0);
        }
      }
    }

    MoveCalculator.prototype.preventLetters = function(e) {
      if (e.which < 48 || e.which > 57) { // Note: `e.which` is part of jQuery
        e.preventDefault();
      }
    }

    // Custom dropdown functionality

    MoveCalculator.prototype.filterCityDropdown = function(e) {
      var $countryDropdown = $(e.currentTarget).parents('.dropdown');
      var selectedCountry, $cities, dataAttr;

      if ($countryDropdown.hasClass('current-country')) {
        selectedCountry = this.getElementValues().currentCountry;
        dataAttr = 'current-country';
        $cities = $(moveContainer + ' .current-city li:not(.filter-container)');
      } else if ($countryDropdown.hasClass('new-country')) {
        selectedCountry = this.getElementValues().newCountry;
        dataAttr = 'new-country';
        $cities = $(moveContainer + ' .new-city li:not(.filter-container)');
      }

      $cities.each(function(index, element) {
        var $element = $(element);
        if ($element.data(dataAttr) === selectedCountry) {
          $element.removeClass('hidden');
        } else {
          $element.addClass('hidden');
        }
      });
    }

    MoveCalculator.prototype.resetCityDropdown = function(e) {
      var $countryDropdown = $(e.currentTarget).parents('.dropdown');
      var $cityDropdownBtn, $filterInput;

      if ($countryDropdown.hasClass('current-country')) {
        $cityDropdownBtn = $(moveContainer + ' .current-city .btn');
        $filterInput = $('.current-city .js-city-filter');
      } else if ($countryDropdown.hasClass('new-country')) {
        $cityDropdownBtn = $(moveContainer + ' .new-city .btn');
        $filterInput = $('.new-city .js-city-filter');
      }

      var $cityDropdownTitle = $cityDropdownBtn.find('.title');
      $filterInput.val('');
      $cityDropdownBtn.removeClass('disabled');
      $cityDropdownTitle.text('--');
      $cityDropdownTitle.data('selected', '');
      $cityDropdownBtn.find('.subtitle').text('');
      this.renderInvalidCompensation();
      this.renderFormula();
    }

    // Render functions

    MoveCalculator.prototype.getData = function() {
      var deferred = jQuery.Deferred();

      $.get('/salary/data.json').then(function(data) {
        // Cache the data
        this.data = data;
        deferred.resolve();
      }.bind(this));

      return deferred.promise();
    }

    MoveCalculator.prototype.render = function(e) {
      var input = this.getElementValues();

      function renderData() {
        if (input.currentCountry && input.newCountry) {
          if (input.currentCity && input.newCity && input.salary) {
            this.renderCompensation(input);
          }

          this.renderContractType();
        } else {
          this.renderInvalidCompensation();
        }

        this.renderFormula();
      }

      if (this.data) {
        renderData.call(this);
      } else {
        $.when(this.getData()).then(function() {
            renderData.call(this);
          }.bind(this),
          this.renderInvalidCompensation.bind(this));
      }

      this.renderContainerUpdates(e);
    }

    MoveCalculator.prototype.renderContainerUpdates = function(e) {
      var $currentEl = $(e.currentTarget);

      if ($currentEl.attr('id') === 'current-salary') {
        $currentEl.parent().toggleClass('has-value', $(e.currentTarget).val().length > 0);
      }
    }

    MoveCalculator.prototype.renderCompensation = function(input) {
      var numbeo = this.data.numbeo;
      var contracts = this.data.contractTypes;

      var salary = input.salary;

      var currentRentIndex = this.calculateRentIndex(input.currentCity, input.currentCountry);
      var currentContract = this.calculateContractFactor(input.currentCountry);
      var newRentIndex = this.calculateRentIndex(input.newCity, input.newCountry);
      var newContract = this.calculateContractFactor(input.newCountry);

      if (!currentRentIndex ||
          !newRentIndex) {
        //When the city and country combination selected do not exist
        return this.renderInvalidCompensation();
      }

      var newSalary = this.calculateCompensation(salary, currentRentIndex, newRentIndex, currentContract.factor, newContract.factor);
      $(compensationAmount).text(this.formatAmount(newSalary) + ' USD');
    }

    MoveCalculator.prototype.renderContractType = function() {
      var values = this.getElementValues();
      var newCountry = values.newCountry;
      var newContract = this.calculateContractFactor(newCountry);
      var $container = $('.contract-type-container');

      if (newContract.type === CONTRACT_TYPE_EMPLOYEE) {
        $container.find('.grammer').text('an');
        $container.find('.company-type').text('Inc.');
        $container.find('.contract-type').text('employee');
      } else {
        $container.find('.grammer').text('a');
        $container.find('.company-type').text('BV.');
        $container.find('.contract-type').text('contractor');
      }

      $container.removeClass('hidden');
    }

    MoveCalculator.prototype.renderFormula = function() {
      var values = this.getElementValues();
      var currentRentIndex = this.calculateRentIndex(values.currentCity, values.currentCountry);
      var newRentIndex = this.calculateRentIndex(values.newCity, values.newCountry);
      var currentContract = this.calculateContractFactor(values.currentCountry);
      var newContract = this.calculateContractFactor(values.newCountry);
      var salary = values.salary;

      $('.formula .currentSalary .value').text(this.formatAmount(salary));
      $('.formula .currentRentIndex .value').text(currentRentIndex ? currentRentIndex : defaultValue);
      $('.formula .newRentIndex .value').text(newRentIndex ? newRentIndex : defaultValue);
      $('.formula .currentContractTypeFactor .value').text(currentContract.hasOwnProperty('factor') ? currentContract.factor.toFixed(2) : defaultValue);
      $('.formula .newContractTypeFactor .value').text(newContract.hasOwnProperty('factor') ? newContract.factor.toFixed(2) : defaultValue);
    }

    MoveCalculator.prototype.renderInvalidCompensation = function() {
      $(compensationAmount).text(defaultValue);
    }

    // Pure functions

    MoveCalculator.prototype.getElementValues = function() {
      return {
        salary: $(moveContainer + ' .current-salary input').val() || '',
        currentCountry: $(moveContainer + ' .current-country .title').data('selected') || '',
        currentCity: $(moveContainer + ' .current-city .title').data('selected') || '',
        newCountry: $(moveContainer + ' .new-country .title').data('selected') || '',
        newCity: $(moveContainer + ' .new-city .title').data('selected') || ''
      };
    }

    MoveCalculator.prototype.calculateRentIndex = function(city, country) {
      if (this.data && this.data.numbeo) {
        var locationData = this.data.numbeo.find(function(o) {
          return o.country === country && o.city === city;
        });

        return locationData ? parseFloat((locationData.rentIndex * 0.01).toFixed(2)) : 0;
      }

      // Rent Index will never be zero, safe to return as error value
      return 0;
    }

    MoveCalculator.prototype.calculateContractFactor = function(country) {
      var contract = {};

      if (this.data && this.data.contractTypes) {
        var defaultContractorFactor = this.data.contractTypes.find(function (o) {
          return o.country === '*';
        }).contractor_factor;

        var contractType = this.data.contractTypes.find(function(o) {
          return o.country === country
        });

        if (contractType && contractType.hasOwnProperty(CONTRACT_TYPE_EMPLOYEE)) {
          contract.type = CONTRACT_TYPE_EMPLOYEE;
          contract.factor = contractType[CONTRACT_TYPE_EMPLOYEE];
        } else if (contractType) {
          contract.type = CONTRACT_TYPE_CONTRACTOR;
          contract.factor = contractType[CONTRACT_TYPE_CONTRACTOR];
        } else {
          contract.type = CONTRACT_TYPE_CONTRACTOR;
          contract.factor = defaultContractorFactor;
        }
      }

      return contract;
    }

    MoveCalculator.prototype.calculateCompensation = function(salary, currentRentIndex, newRentIndex, currentContractTypeFactor, newContractTypeFactor) {
      return Math.round(salary * (newRentIndex + 0.25) / (currentRentIndex + 0.25) * (newContractTypeFactor / currentContractTypeFactor));
    };

    MoveCalculator.prototype.formatAmount = function(amount) {
      return '$' + amount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
    }

    return MoveCalculator;
  })();

  new MoveCalculator();
})();
<commit_msg>Update comment to explain use of `setTimeout`<commit_after>(function () {
  var moveContainer = '.move-container';
  var formulaContainer = '.formula-container';
  var compensationAmount = moveContainer + ' .compensation .amount';
  var defaultValue = '--';

  var CONTRACT_TYPE_EMPLOYEE = 'employee_factor';
  var CONTRACT_TYPE_CONTRACTOR = 'contractor_factor';

  // Dropdown Core functionality

  var setDropdown = function(e) {
    var $selected = $(e.currentTarget);

    if ($selected.hasClass('filter-container')) {
      return;
    }

    var key = $selected.find('.key').text();
    var value = $selected.find('.value').text();
    var displayValue = $selected.find('.display-value').text();
    var $title = $selected.parents('.dropdown').find('.title');
    var $subtitle = $selected.parents('.dropdown').find('.subtitle');

    $title.text(key);
    $title.data('selected', value || key);

    if (value) {
      $subtitle.text(value);
    } else if (displayValue) {
      $subtitle.text(displayValue);
    }
  }

  var isFullyVisible = function(el, parentEl) {
    var parentHeight = parentEl.height() - el.height(); // Make sure last item is not partially visible
    var elTop = el.position().top;
    var elBottom = elTop + (el.height());

    return (elBottom > 0 && elTop < parentHeight);
  }

  this.MoveCalculator = (function() {
    function MoveCalculator() {
      this.bindEvents();
    }

    MoveCalculator.prototype.bindEvents = function() {
      var $currentCountryDropdownContainer = $(moveContainer + ' .current-country');
      var $currentCountryDropdown = $currentCountryDropdownContainer.find('li:not(.filter-container)');
      var $currentCountryFilter = $currentCountryDropdownContainer.find('.js-country-filter');
      var $newCountryDropdownContainer = $(moveContainer + ' .new-country');
      var $newCountryDropdown = $newCountryDropdownContainer.find('li:not(.filter-container)');
      var $newCountryFilter = $newCountryDropdownContainer.find('.js-country-filter');

      var $currentCityDropdownContainer = $(moveContainer + ' .current-city');
      var $currentCityDropdown = $currentCityDropdownContainer.find('li:not(.filter-container)');
      var $currentCityFilter = $currentCityDropdownContainer.find('.js-city-filter');
      var $newCityDropdownContainer = $(moveContainer + ' .new-city');
      var $newCityDropdown = $newCityDropdownContainer.find('li:not(.filter-container)');
      var $newCityFilter = $newCityDropdownContainer.find('.js-city-filter');

      var $currentSalary = $(moveContainer + ' .current-salary input');

      // Set selected dropdown value
      $currentCountryDropdown.on('click', setDropdown);
      $currentCityDropdown.on('click', setDropdown);
      $newCountryDropdown.on('click', setDropdown);
      $newCityDropdown.on('click', setDropdown);

      // Render compensation
      $currentSalary.on('keyup', this.render.bind(this));
      $currentCountryDropdown.on('click', this.render.bind(this));
      $currentCityDropdown.on('click', this.render.bind(this));
      $newCountryDropdown.on('click', this.render.bind(this));
      $newCityDropdown.on('click', this.render.bind(this));

      // Unlock and filter city dropdown
      $currentCountryDropdown.on('click', this.filterCityDropdown.bind(this));
      $currentCountryDropdown.on('click', this.resetCityDropdown.bind(this));
      $newCountryDropdown.on('click', this.filterCityDropdown.bind(this));
      $newCountryDropdown.on('click', this.resetCityDropdown.bind(this));

      // Highlighting for Filter
      $currentCountryDropdownContainer.on('keydown', this.highlightDropdownItem.bind(this));
      $currentCityDropdownContainer.on('keydown', this.highlightDropdownItem.bind(this));
      $newCountryDropdownContainer.on('keydown', this.highlightDropdownItem.bind(this));
      $newCityDropdownContainer.on('keydown', this.highlightDropdownItem.bind(this));

      // Filtering
      $currentCountryFilter.on('keyup', this.search.bind(null, {
        dropdown: 'current-country'
      }));
      $newCountryFilter.on('keyup', this.search.bind(null, {
        dropdown: 'new-country'
      }));

      $currentCityFilter.on('keyup', this.search.bind(this, {
        dropdown: 'current-city',
        filter: 'current-country',
      }));
      $newCityFilter.on('keyup', this.search.bind(this, {
        dropdown: 'new-city',
        filter: 'new-country',
      }));

      $currentSalary.on('keypress', this.preventLetters.bind(this));

      $currentCountryDropdownContainer.on('shown.bs.dropdown', this.focusInput);
      $newCountryDropdownContainer.on('shown.bs.dropdown', this.focusInput);
      $currentCityDropdownContainer.on('shown.bs.dropdown', this.focusInput);
      $newCityDropdownContainer.on('shown.bs.dropdown', this.focusInput);
    }

    MoveCalculator.prototype.focusInput = function(e) {
      $(e.target).find('.filter-input').focus();
    }

    MoveCalculator.prototype.search = function(data, e) {
      var text = e.target.value.toLowerCase();
      var filterValue = null;
      var $items = $(moveContainer + ' .' + data.dropdown + ' li:not(.filter-container)');
      var filterName;

      if (data.filter) {
        filterName = data.filter === 'current-country' ? 'currentCountry' : 'newCountry';
        filterValue = this.getElementValues()[filterName];
      }

      $items.each(function(index, element) {
        var $element = $(element);
        var value = $element.children('.key').text().toLowerCase();
        var searchableElement = true;

        if (data.filter && filterValue) {
          searchableElement = $element.data(data.filter.toLowerCase()) === filterValue;
        }

        if (value.indexOf(text) == -1 || !searchableElement) {
          $element.addClass('hidden');
        } else {
          $element.removeClass('hidden');
        }
      });
    }

    MoveCalculator.prototype.highlightDropdownItem = function(e) {
      var $this = $(e.currentTarget);
      var listContainer = $this.find('.dropdown-menu.dropdown-scroll');
      var list = $this.find('li:not(.hidden):not(.filter-container)');
      var nextLi, prevLi, focusedLi;

      if ($this.hasClass('open')) {
        if ((e.keyCode === 38 || e.keyCode === 40) &&
            !list.filter('li.is-focused').length) {
          list.first().addClass('is-focused');
          return;
        }

        if (e.keyCode === 38 && !list.first().hasClass('is-focused')) { // Up
          prevLi = list
                    .filter('li.is-focused')
                    .prevAll('li:not(.hidden)').first();

          list.removeClass('is-focused');

          if (prevLi[0] === list.first()[0]) {
            list.first().addClass('is-focused');
          } else {
            prevLi.addClass('is-focused');
          }
        } else if (e.keyCode === 40 && !list.last().hasClass('is-focused')) { // Down
          nextLi = list
                    .filter('li.is-focused')
                    .nextAll('li:not(.hidden)').first();

          list.removeClass('is-focused');

          if (nextLi[0] === list.last()[0]) {
            list.last().addClass('is-focused');
          } else {
            nextLi.addClass('is-focused');
          }
        } else if (e.keyCode === 9) { // Tab keyCode
          $this.removeClass('open');
        } else if (e.keyCode === 13) { // Enter / Return
          // As user is highlighting list items using up/down arrow keys
          // we have to wait until effects of `addClass()` are completed
          // otherwise we won't get any results on `list.filter()` call
          setTimeout(function() { list.filter('li.is-focused').trigger('click'); });
        }

        focusedLi = list.filter('li.is-focused');
        if (focusedLi.length && list.first()[0] !== focusedLi[0]) {
          if (!isFullyVisible(focusedLi, listContainer)) {
            listContainer.scrollTop(list.index(focusedLi) * focusedLi.outerHeight());
          }
        }
        else {
          listContainer.scrollTop(0);
        }
      }
    }

    MoveCalculator.prototype.preventLetters = function(e) {
      if (e.which < 48 || e.which > 57) { // Note: `e.which` is part of jQuery
        e.preventDefault();
      }
    }

    // Custom dropdown functionality

    MoveCalculator.prototype.filterCityDropdown = function(e) {
      var $countryDropdown = $(e.currentTarget).parents('.dropdown');
      var selectedCountry, $cities, dataAttr;

      if ($countryDropdown.hasClass('current-country')) {
        selectedCountry = this.getElementValues().currentCountry;
        dataAttr = 'current-country';
        $cities = $(moveContainer + ' .current-city li:not(.filter-container)');
      } else if ($countryDropdown.hasClass('new-country')) {
        selectedCountry = this.getElementValues().newCountry;
        dataAttr = 'new-country';
        $cities = $(moveContainer + ' .new-city li:not(.filter-container)');
      }

      $cities.each(function(index, element) {
        var $element = $(element);
        if ($element.data(dataAttr) === selectedCountry) {
          $element.removeClass('hidden');
        } else {
          $element.addClass('hidden');
        }
      });
    }

    MoveCalculator.prototype.resetCityDropdown = function(e) {
      var $countryDropdown = $(e.currentTarget).parents('.dropdown');
      var $cityDropdownBtn, $filterInput;

      if ($countryDropdown.hasClass('current-country')) {
        $cityDropdownBtn = $(moveContainer + ' .current-city .btn');
        $filterInput = $('.current-city .js-city-filter');
      } else if ($countryDropdown.hasClass('new-country')) {
        $cityDropdownBtn = $(moveContainer + ' .new-city .btn');
        $filterInput = $('.new-city .js-city-filter');
      }

      var $cityDropdownTitle = $cityDropdownBtn.find('.title');
      $filterInput.val('');
      $cityDropdownBtn.removeClass('disabled');
      $cityDropdownTitle.text('--');
      $cityDropdownTitle.data('selected', '');
      $cityDropdownBtn.find('.subtitle').text('');
      this.renderInvalidCompensation();
      this.renderFormula();
    }

    // Render functions

    MoveCalculator.prototype.getData = function() {
      var deferred = jQuery.Deferred();

      $.get('/salary/data.json').then(function(data) {
        // Cache the data
        this.data = data;
        deferred.resolve();
      }.bind(this));

      return deferred.promise();
    }

    MoveCalculator.prototype.render = function(e) {
      var input = this.getElementValues();

      function renderData() {
        if (input.currentCountry && input.newCountry) {
          if (input.currentCity && input.newCity && input.salary) {
            this.renderCompensation(input);
          }

          this.renderContractType();
        } else {
          this.renderInvalidCompensation();
        }

        this.renderFormula();
      }

      if (this.data) {
        renderData.call(this);
      } else {
        $.when(this.getData()).then(function() {
            renderData.call(this);
          }.bind(this),
          this.renderInvalidCompensation.bind(this));
      }

      this.renderContainerUpdates(e);
    }

    MoveCalculator.prototype.renderContainerUpdates = function(e) {
      var $currentEl = $(e.currentTarget);

      if ($currentEl.attr('id') === 'current-salary') {
        $currentEl.parent().toggleClass('has-value', $(e.currentTarget).val().length > 0);
      }
    }

    MoveCalculator.prototype.renderCompensation = function(input) {
      var numbeo = this.data.numbeo;
      var contracts = this.data.contractTypes;

      var salary = input.salary;

      var currentRentIndex = this.calculateRentIndex(input.currentCity, input.currentCountry);
      var currentContract = this.calculateContractFactor(input.currentCountry);
      var newRentIndex = this.calculateRentIndex(input.newCity, input.newCountry);
      var newContract = this.calculateContractFactor(input.newCountry);

      if (!currentRentIndex ||
          !newRentIndex) {
        //When the city and country combination selected do not exist
        return this.renderInvalidCompensation();
      }

      var newSalary = this.calculateCompensation(salary, currentRentIndex, newRentIndex, currentContract.factor, newContract.factor);
      $(compensationAmount).text(this.formatAmount(newSalary) + ' USD');
    }

    MoveCalculator.prototype.renderContractType = function() {
      var values = this.getElementValues();
      var newCountry = values.newCountry;
      var newContract = this.calculateContractFactor(newCountry);
      var $container = $('.contract-type-container');

      if (newContract.type === CONTRACT_TYPE_EMPLOYEE) {
        $container.find('.grammer').text('an');
        $container.find('.company-type').text('Inc.');
        $container.find('.contract-type').text('employee');
      } else {
        $container.find('.grammer').text('a');
        $container.find('.company-type').text('BV.');
        $container.find('.contract-type').text('contractor');
      }

      $container.removeClass('hidden');
    }

    MoveCalculator.prototype.renderFormula = function() {
      var values = this.getElementValues();
      var currentRentIndex = this.calculateRentIndex(values.currentCity, values.currentCountry);
      var newRentIndex = this.calculateRentIndex(values.newCity, values.newCountry);
      var currentContract = this.calculateContractFactor(values.currentCountry);
      var newContract = this.calculateContractFactor(values.newCountry);
      var salary = values.salary;

      $('.formula .currentSalary .value').text(this.formatAmount(salary));
      $('.formula .currentRentIndex .value').text(currentRentIndex ? currentRentIndex : defaultValue);
      $('.formula .newRentIndex .value').text(newRentIndex ? newRentIndex : defaultValue);
      $('.formula .currentContractTypeFactor .value').text(currentContract.hasOwnProperty('factor') ? currentContract.factor.toFixed(2) : defaultValue);
      $('.formula .newContractTypeFactor .value').text(newContract.hasOwnProperty('factor') ? newContract.factor.toFixed(2) : defaultValue);
    }

    MoveCalculator.prototype.renderInvalidCompensation = function() {
      $(compensationAmount).text(defaultValue);
    }

    // Pure functions

    MoveCalculator.prototype.getElementValues = function() {
      return {
        salary: $(moveContainer + ' .current-salary input').val() || '',
        currentCountry: $(moveContainer + ' .current-country .title').data('selected') || '',
        currentCity: $(moveContainer + ' .current-city .title').data('selected') || '',
        newCountry: $(moveContainer + ' .new-country .title').data('selected') || '',
        newCity: $(moveContainer + ' .new-city .title').data('selected') || ''
      };
    }

    MoveCalculator.prototype.calculateRentIndex = function(city, country) {
      if (this.data && this.data.numbeo) {
        var locationData = this.data.numbeo.find(function(o) {
          return o.country === country && o.city === city;
        });

        return locationData ? parseFloat((locationData.rentIndex * 0.01).toFixed(2)) : 0;
      }

      // Rent Index will never be zero, safe to return as error value
      return 0;
    }

    MoveCalculator.prototype.calculateContractFactor = function(country) {
      var contract = {};

      if (this.data && this.data.contractTypes) {
        var defaultContractorFactor = this.data.contractTypes.find(function (o) {
          return o.country === '*';
        }).contractor_factor;

        var contractType = this.data.contractTypes.find(function(o) {
          return o.country === country
        });

        if (contractType && contractType.hasOwnProperty(CONTRACT_TYPE_EMPLOYEE)) {
          contract.type = CONTRACT_TYPE_EMPLOYEE;
          contract.factor = contractType[CONTRACT_TYPE_EMPLOYEE];
        } else if (contractType) {
          contract.type = CONTRACT_TYPE_CONTRACTOR;
          contract.factor = contractType[CONTRACT_TYPE_CONTRACTOR];
        } else {
          contract.type = CONTRACT_TYPE_CONTRACTOR;
          contract.factor = defaultContractorFactor;
        }
      }

      return contract;
    }

    MoveCalculator.prototype.calculateCompensation = function(salary, currentRentIndex, newRentIndex, currentContractTypeFactor, newContractTypeFactor) {
      return Math.round(salary * (newRentIndex + 0.25) / (currentRentIndex + 0.25) * (newContractTypeFactor / currentContractTypeFactor));
    };

    MoveCalculator.prototype.formatAmount = function(amount) {
      return '$' + amount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
    }

    return MoveCalculator;
  })();

  new MoveCalculator();
})();
<|endoftext|>