<commit_before>
import React, { Component, Children } from 'react';
import classNames from 'classnames';

import Token from './Token';
import { BACKSPACE, ENTER, LEFT, RIGHT, DOWN, UP } from './key-codes';
import { isSelectToHome, isSelectToEnd } from './key-utils';

const DIRECTION_NONE = 'none';
const DIRECTION_BACKWARD = 'backward';
const DIRECTION_FORWARD = 'forward';

const INPUT_STYLE = {
  font: 'inherit',
  lineHeight: 'inherit',
  height: 'auto',
  padding: 0,
  border: 'none',
  flexGrow: 1,
  outline: 'none'
};

const DEFAULT_PROPS = {
  defaultTokens: []
};

export default class FacetedTokenInput extends Component {

  constructor(props) {
    super(props);

    this.state = {
      focused: false,
      searchText: '',
      tokens: props.defaultTokens || [],
      showDropDown: false,
      selectedSectionIndex: -1,
      selectedIndex: -1,
      selectedId: null,
      tokenSelectionDirection: 'none',
      tokenSelectionStart: -1,
      tokenSelectionEnd: -1
    };
  }

  render() {
    const { placeholder } = this.props;

    const { tokens, searchText, showDropDown, focused } = this.state;

    const FacetedTokenInputClass = classNames("compound-input", {
      "focused": focused
    });

    return (

 this.onKeyDown(event) }
        onFocus={ event => this.onFocus(event) }
        onBlur={ event => this.onBlur(event) }
      >
        { tokens.map(this.renderToken, this) }

        <input
          ref="input"
          style={ INPUT_STYLE }
          className="compound-input-field"
          placeholder={ tokens.length ? '' : placeholder }
          value={ searchText }
          onChange={ event => this.onChange(event) }
          onFocus={ event => this.onInputFocus(event) }
        />

        { this.props.children }

        { showDropDown && this.renderDropdown() }

    );
  }

  renderDropdown() {
    const { dropdownSections } = this.props;

    if (!dropdownSections || !dropdownSections.length) {
      return null;
    }

    return (

        { dropdownSections.map(this.renderDropdownSection, this) }

    );
  }

  renderDropdownSection(section, sectionIndex) {
    const { selectedId, selectedSectionIndex, selectedIndex } = this.state;

    const { title, suggestions } = section;

    if (!suggestions || !suggestions.length) {
      return null;
    }

    return (

        { title &&
{ title }
 }

        { suggestions.map((suggestion, index) => {
          const selected =
            (selectedId === suggestion.id) ||
            (
              (selectedSectionIndex === sectionIndex) &&
              (selectedIndex === index )
            );

          return (

               this.addToken(suggestion.result) }
                onMouseMove={ event => this.setState({
                  selectedSectionIndex: sectionIndex,
                  selectedIndex: index
                })}
              >
                { suggestion.description }

          );
        })}

    );
  }

  renderToken(token, index) {
    const { facet, description, dropdownMenu } = this.props.renderToken(token);

    return (
      <Token
        ref={ 'token' + index }
        index={ index }
        selected={ this.isInTokenSelection(index) }
        token={ token }
        facet={ facet }
        description={ description }
        dropdownMenu={ dropdownMenu }
        onUpdate={ event => this.updateToken(event.token, event.index) }
        onKeyDown={ event => this.onKeyDown(event) }
        onFocus={ event => this.onTokenFocus(event, index) }
        onShowDropdown={ event => this.setState({ showDropDown: false }) }
      />
    )
  }

  componentDidUpdate() {
    if (this.state.focused) {
      const {
        tokens,
        tokenSelectionDirection,
        tokenSelectionStart,
        tokenSelectionEnd
      } = this.state;

      const noSelection = (tokenSelectionStart < 0) && (tokenSelectionEnd < 0);

      if (noSelection || this.isInTokenSelection(tokens.length)) {
        this.refs.input.focus();
      }
      else if (tokenSelectionDirection === DIRECTION_NONE) {
        this.refs['token' + tokenSelectionStart].focus();
      }
      else {
        this.refs.FacetedTokenInput.focus();
      }
    }
  }

  isInTokenSelection(index) {
    return (
      (index >= this.state.tokenSelectionStart) &&
      (index < this.state.tokenSelectionEnd)
    );
  }

  onInputFocus(event) {
    const { tokens } = this.state;

    if (!this.isInTokenSelection(tokens.length)) {
      this.onTokenFocus(event, tokens.length);
    }
  }

  onChange(event) {
    const { tokens } = this.state;

    const searchText = this.refs.input.value;

    this.setState({
      searchText: searchText,
      showDropDown: true,
      selectedSectionIndex: -1,
      selectedIndex: -1
    });

    this.props.onChange({ tokens, searchText })

    this.onTokenFocus(event, tokens.length);
  }

  onTokenFocus(event, index) {
    this.setState({
      tokenSelectionDirection: DIRECTION_NONE,
      tokenSelectionStart: index,
      tokenSelectionEnd: index + 1
    });
  }

  onFocus(event) {
    this.setState({ focused: true });
  }

  onBlur(event) {
    this.setState({ focused: false });

    setTimeout(() => {
      // focus out
      if (!this.state.focused) {
        this.setState({
          showDropDown: false,
          tokenSelectionDirection: DIRECTION_NONE,
          tokenSelectionStart: -1,
          tokenSelectionEnd: -1
        })
      }
    }, 0);
  }

  onKeyDown(event) {
    switch (event.which) {
      case ENTER:
        return this.onEnter(event);
      case BACKSPACE:
        return this.onBackspace(event);
      case UP:
      case DOWN:
        return this.onUpDown(event);
      case LEFT:
      case RIGHT:
        return this.onLeftRight(event);
    }
  }

  onUpDown(event) {
    const {
      tokens,
      showDropDown,
      selectedSectionIndex,
      selectedIndex
    } = this.state;

    const { dropdownSections } = this.props;

    if (showDropDown && dropdownSections) {
      event.preventDefault();

      let nextSelectedSectionIndex;
      let nextSelectedIndex;

      const section = dropdownSections[selectedSectionIndex];

      if (event.which === DOWN) {
        nextSelectedSectionIndex = selectedSectionIndex;
        nextSelectedIndex = selectedIndex + 1;

        if (!section || (section.suggestions.length <= nextSelectedIndex)) {
          if (selectedSectionIndex < (dropdownSections.length - 1)) {
            nextSelectedSectionIndex += 1;
            nextSelectedIndex = 0;
          }
        }
      }
      else if (event.which === UP) {
        nextSelectedSectionIndex = selectedSectionIndex;
        nextSelectedIndex = selectedIndex - 1;

        if (nextSelectedIndex < 0) {
          if (selectedSectionIndex <= 0) {
            nextSelectedSectionIndex = -1;
            nextSelectedIndex = -1;
          }
          else {
            nextSelectedSectionIndex = selectedSectionIndex - 1;
            nextSelectedIndex =
              dropdownSections[nextSelectedSectionIndex].suggestions.length - 1;
          }
        }
      }

      nextSelectedSectionIndex = Math.max(
        -1, Math.min(dropdownSections.length - 1, nextSelectedSectionIndex)
      );

      const nextSection = dropdownSections[nextSelectedSectionIndex];

      nextSelectedIndex = Math.max(
        -1, Math.min(
          nextSection ? (nextSection.suggestions.length - 1) : 0,
          nextSelectedIndex
        )
      );

      this.setState({
        selectedSectionIndex: nextSelectedSectionIndex,
        selectedIndex: nextSelectedIndex
      });
    }
    else if (this.isInTokenSelection(tokens.length)) {
      this.setState({
        tokenSelectionDirection: DIRECTION_NONE,
        tokenSelectionStart: tokens.length,
        tokenSelectionEnd: tokens.length + 1,
      });
    }
  }

  onLeftRight(event) {
    const {
      selectionStart,
      selectionEnd,
      selectionDirection
    } = this.refs.input;

    const hasCaret = (selectionStart === selectionEnd);

    let {
      tokens,
      tokenSelectionDirection,
      tokenSelectionEnd,
      tokenSelectionStart
    } = this.state;

    const hasTokenCaret = (tokenSelectionStart === tokenSelectionEnd)

    // TODO: Manage RTL languages

    const keyDirection =
      (event.which === RIGHT) ? DIRECTION_FORWARD :
      (event.which === LEFT) ? DIRECTION_BACKWARD :
      DIRECTION_NONE;

    const selectToHome = isSelectToHome(event);
    const selectToEnd = isSelectToEnd(event);

    if (!selectToHome && !selectToEnd && tokenSelectionStart >= tokens.length) {
      // The text field is focused

      if (selectionStart > 0) {
        // We’re moving inside the text field
        return;
      }

      if (selectionDirection === DIRECTION_FORWARD) {
        // The 'caret' is on the opposite side
        return;
      }

      if (selectionDirection === DIRECTION_BACKWARD && !event.shiftKey) {
        // We do have a selection but we’re exiting it on the left
        return;
      }

      if (keyDirection === DIRECTION_FORWARD) {
        // We are moving on the opposite side
        return;
      }
    }

    if (!selectToHome && !selectToEnd) {
      if (tokenSelectionEnd <= tokens.length) {
        event.preventDefault();
      }
    }

    if (selectToHome) {
      tokenSelectionStart = 0;

      if ((tokenSelectionEnd - tokenSelectionStart) > 1) {
        if (tokenSelectionDirection === DIRECTION_NONE) {
          tokenSelectionDirection = DIRECTION_BACKWARD;
        }
      }
    }
    else if (selectToEnd) {
      tokenSelectionEnd = tokens.length + 1;

      this.refs.input.setSelectionRange(
        selectionStart, this.refs.input.value.length);

      if (tokenSelectionStart < tokens.length) {
        this.refs.input.setSelectionRange(
          0, this.refs.input.value.length);
      }

      if ((tokenSelectionEnd - tokenSelectionStart) > 1) {
        if (tokenSelectionDirection === DIRECTION_NONE) {
          tokenSelectionDirection = DIRECTION_FORWARD;
        }
      }
    }
    else if (!event.shiftKey) {
      if (tokenSelectionDirection === DIRECTION_NONE) {
        if (keyDirection === DIRECTION_FORWARD) {
          tokenSelectionStart += 1;
          tokenSelectionEnd += 1;
        }
        else if (keyDirection === DIRECTION_BACKWARD) {
          tokenSelectionStart -= 1;
          tokenSelectionEnd -= 1;
        }
      }
      else if (keyDirection === DIRECTION_FORWARD) {
        tokenSelectionStart = tokenSelectionEnd - 1;
      }
      else if (keyDirection === DIRECTION_BACKWARD) {
        tokenSelectionEnd = tokenSelectionStart + 1;
      }

      tokenSelectionDirection = DIRECTION_NONE;

      if (tokenSelectionStart <= tokens.length) {
        this.refs.input.setSelectionRange(0, 0);
      }
    }
    else {
      const initialIsInputInSelection = (tokenSelectionEnd > tokens.length);

      if (tokenSelectionDirection === DIRECTION_NONE) {
        tokenSelectionDirection = keyDirection;
      }

      const increment = (tokenSelectionDirection === keyDirection) ? 1 : -1;

      if (tokenSelectionDirection === DIRECTION_FORWARD) {
        tokenSelectionEnd += increment;
      }
      else if (tokenSelectionDirection === DIRECTION_BACKWARD) {
        tokenSelectionStart -= increment;
      }

      if ((tokenSelectionEnd - tokenSelectionStart) <= 1) {
        tokenSelectionDirection = DIRECTION_NONE;
      }

      if (tokenSelectionEnd > tokens.length) {
        if (keyDirection !== tokenSelectionDirection) {
          event.preventDefault();
        }

        if (!initialIsInputInSelection && (keyDirection === DIRECTION_FORWARD)) {
          this.refs.input.setSelectionRange(0, 1);
        }
      }
    }

    tokenSelectionStart = Math.max(0, tokenSelectionStart);
    tokenSelectionEnd = Math.max(tokenSelectionStart + 1, tokenSelectionEnd);

    tokenSelectionEnd = Math.min(tokens.length + 1, tokenSelectionEnd);
    tokenSelectionStart = Math.min(tokenSelectionEnd - 1, tokenSelectionStart);

    this.setState({
      tokenSelectionDirection,
      tokenSelectionEnd,
      tokenSelectionStart
    });
  }

  onBackspace(event) {
    const { tokens, tokenSelectionStart, tokenSelectionEnd } = this.state;

    this.setState({
      tokens: [
        ...tokens.slice(0, tokenSelectionStart),
        ...tokens.slice(tokenSelectionEnd)
      ]
    });

    const { selectionStart, selectionEnd } = this.refs.input;

    const caretIsAtStart = (selectionStart === 0) && (selectionEnd === 0);

    if (this.isInTokenSelection(tokens.length) && caretIsAtStart) {
      this.setState({
        tokenSelectionDirection: DIRECTION_NONE,
        tokenSelectionStart: tokenSelectionStart - 1,
        tokenSelectionEnd: tokenSelectionStart
      });
    }
    else {
      this.setState({
        tokenSelectionDirection: DIRECTION_NONE,
        tokenSelectionStart: tokenSelectionStart,
        tokenSelectionEnd: tokenSelectionStart + 1
      });
    }

    if (event.target !== this.refs.input) {
      event.preventDefault();
    }
  }

  onEnter(event) {
    const {
      showDropDown,
      selectedSectionIndex,
      selectedIndex
    } = this.state;

    const { dropdownSections } = this.props;

    if (showDropDown) {
      this.setState({
        showDropDown: false
      });

      if (!dropdownSections) {
        return;
      }

      const section = dropdownSections[selectedSectionIndex];

      if (!section) {
        return;
      }

      const suggestion = section.suggestions[selectedIndex];

      if (!suggestion) {
        return;
      }

      this.addToken(suggestion.result);
    }

    /*const { tokens, searchText } = this.state;

    if (!searchText.trim()) {
      return;
    }

    const nextTokens = [
      ...tokens,
      { label: searchText } // his.props.getToken(searchText)
    ];

    this.setState({
      searchText: '',
      tokens: nextTokens,
      tokenSelectionDirection: DIRECTION_NONE,
      tokenSelectionStart: tokens.length + 1,
      tokenSelectionEnd: tokens.length + 2
    });

    this.props.onChange({ searchText: '', tokens: nextTokens });*/
  }

  addToken(token) {
    const { tokens } = this.state;

    const nextTokens = tokens.concat([ token ]);

    this.setState({
      searchText: '',
      showDropDown: false,
      tokens: nextTokens,
      tokenSelectionDirection: DIRECTION_NONE,
      tokenSelectionStart: tokens.length + 1,
      tokenSelectionEnd: tokens.length + 2
    });

    this.props.onChange({
      searchText: '',
      tokens: nextTokens
    });
  }

  updateToken(token, index) {
    const { tokens } = this.state;

    this.setState({
      tokens: [
        ...tokens.slice(0, index),
        token,
        ...tokens.slice(index + 1)
      ]
    })
  }

};

FacetedTokenInput.defaultProps = DEFAULT_PROPS;
<commit_msg>Add separator between dropdown sections<commit_after>
import React, { Component, Children } from 'react';
import classNames from 'classnames';

import Token from './Token';
import { BACKSPACE, ENTER, LEFT, RIGHT, DOWN, UP } from './key-codes';
import { isSelectToHome, isSelectToEnd } from './key-utils';

const DIRECTION_NONE = 'none';
const DIRECTION_BACKWARD = 'backward';
const DIRECTION_FORWARD = 'forward';

const INPUT_STYLE = {
  font: 'inherit',
  lineHeight: 'inherit',
  height: 'auto',
  padding: 0,
  border: 'none',
  flexGrow: 1,
  outline: 'none'
};

const DEFAULT_PROPS = {
  defaultTokens: []
};

export default class FacetedTokenInput extends Component {

  constructor(props) {
    super(props);

    this.state = {
      focused: false,
      searchText: '',
      tokens: props.defaultTokens || [],
      showDropDown: false,
      selectedSectionIndex: -1,
      selectedIndex: -1,
      selectedId: null,
      tokenSelectionDirection: 'none',
      tokenSelectionStart: -1,
      tokenSelectionEnd: -1
    };
  }

  render() {
    const { placeholder } = this.props;

    const { tokens, searchText, showDropDown, focused } = this.state;

    const FacetedTokenInputClass = classNames("compound-input", {
      "focused": focused
    });

    return (

 this.onKeyDown(event) }
        onFocus={ event => this.onFocus(event) }
        onBlur={ event => this.onBlur(event) }
      >
        { tokens.map(this.renderToken, this) }

        <input
          ref="input"
          style={ INPUT_STYLE }
          className="compound-input-field"
          placeholder={ tokens.length ? '' : placeholder }
          value={ searchText }
          onChange={ event => this.onChange(event) }
          onFocus={ event => this.onInputFocus(event) }
        />

        { this.props.children }

        { showDropDown && this.renderDropdown() }

    );
  }

  renderDropdown() {
    const { dropdownSections } = this.props;

    if (!dropdownSections || !dropdownSections.length) {
      return null;
    }

    return (

        {
          dropdownSections
            .map(this.renderDropdownSection, this)
            .reduce((memo, o) => [ ...memo,
, o ], [])
            .slice(1)
        }

    );
  }

  renderDropdownSection(section, sectionIndex) {
    const { selectedId, selectedSectionIndex, selectedIndex } = this.state;

    const { title, suggestions } = section;

    if (!suggestions || !suggestions.length) {
      return null;
    }

    return (

        { title &&
{ title }
 }

        { suggestions.map((suggestion, index) => {
          const selected =
            (selectedId === suggestion.id) ||
            (
              (selectedSectionIndex === sectionIndex) &&
              (selectedIndex === index )
            );

          return (

               this.addToken(suggestion.result) }
                onMouseMove={ event => this.setState({
                  selectedSectionIndex: sectionIndex,
                  selectedIndex: index
                })}
              >
                { suggestion.description }

          );
        })}

    );
  }

  renderToken(token, index) {
    const { facet, description, dropdownMenu } = this.props.renderToken(token);

    return (
      <Token
        ref={ 'token' + index }
        index={ index }
        selected={ this.isInTokenSelection(index) }
        token={ token }
        facet={ facet }
        description={ description }
        dropdownMenu={ dropdownMenu }
        onUpdate={ event => this.updateToken(event.token, event.index) }
        onKeyDown={ event => this.onKeyDown(event) }
        onFocus={ event => this.onTokenFocus(event, index) }
        onShowDropdown={ event => this.setState({ showDropDown: false }) }
      />
    )
  }

  componentDidUpdate() {
    if (this.state.focused) {
      const {
        tokens,
        tokenSelectionDirection,
        tokenSelectionStart,
        tokenSelectionEnd
      } = this.state;

      const noSelection = (tokenSelectionStart < 0) && (tokenSelectionEnd < 0);

      if (noSelection || this.isInTokenSelection(tokens.length)) {
        this.refs.input.focus();
      }
      else if (tokenSelectionDirection === DIRECTION_NONE) {
        this.refs['token' + tokenSelectionStart].focus();
      }
      else {
        this.refs.FacetedTokenInput.focus();
      }
    }
  }

  isInTokenSelection(index) {
    return (
      (index >= this.state.tokenSelectionStart) &&
      (index < this.state.tokenSelectionEnd)
    );
  }

  onInputFocus(event) {
    const { tokens } = this.state;

    if (!this.isInTokenSelection(tokens.length)) {
      this.onTokenFocus(event, tokens.length);
    }
  }

  onChange(event) {
    const { tokens } = this.state;

    const searchText = this.refs.input.value;

    this.setState({
      searchText: searchText,
      showDropDown: true,
      selectedSectionIndex: -1,
      selectedIndex: -1
    });

    this.props.onChange({ tokens, searchText })

    this.onTokenFocus(event, tokens.length);
  }

  onTokenFocus(event, index) {
    this.setState({
      tokenSelectionDirection: DIRECTION_NONE,
      tokenSelectionStart: index,
      tokenSelectionEnd: index + 1
    });
  }

  onFocus(event) {
    this.setState({ focused: true });
  }

  onBlur(event) {
    this.setState({ focused: false });

    setTimeout(() => {
      // focus out
      if (!this.state.focused) {
        this.setState({
          showDropDown: false,
          tokenSelectionDirection: DIRECTION_NONE,
          tokenSelectionStart: -1,
          tokenSelectionEnd: -1
        })
      }
    }, 0);
  }

  onKeyDown(event) {
    switch (event.which) {
      case ENTER:
        return this.onEnter(event);
      case BACKSPACE:
        return this.onBackspace(event);
      case UP:
      case DOWN:
        return this.onUpDown(event);
      case LEFT:
      case RIGHT:
        return this.onLeftRight(event);
    }
  }

  onUpDown(event) {
    const {
      tokens,
      showDropDown,
      selectedSectionIndex,
      selectedIndex
    } = this.state;

    const { dropdownSections } = this.props;

    if (showDropDown && dropdownSections) {
      event.preventDefault();

      let nextSelectedSectionIndex;
      let nextSelectedIndex;

      const section = dropdownSections[selectedSectionIndex];

      if (event.which === DOWN) {
        nextSelectedSectionIndex = selectedSectionIndex;
        nextSelectedIndex = selectedIndex + 1;

        if (!section || (section.suggestions.length <= nextSelectedIndex)) {
          if (selectedSectionIndex < (dropdownSections.length - 1)) {
            nextSelectedSectionIndex += 1;
            nextSelectedIndex = 0;
          }
        }
      }
      else if (event.which === UP) {
        nextSelectedSectionIndex = selectedSectionIndex;
        nextSelectedIndex = selectedIndex - 1;

        if (nextSelectedIndex < 0) {
          if (selectedSectionIndex <= 0) {
            nextSelectedSectionIndex = -1;
            nextSelectedIndex = -1;
          }
          else {
            nextSelectedSectionIndex = selectedSectionIndex - 1;
            nextSelectedIndex =
              dropdownSections[nextSelectedSectionIndex].suggestions.length - 1;
          }
        }
      }

      nextSelectedSectionIndex = Math.max(
        -1, Math.min(dropdownSections.length - 1, nextSelectedSectionIndex)
      );

      const nextSection = dropdownSections[nextSelectedSectionIndex];

      nextSelectedIndex = Math.max(
        -1, Math.min(
          nextSection ? (nextSection.suggestions.length - 1) : 0,
          nextSelectedIndex
        )
      );

      this.setState({
        selectedSectionIndex: nextSelectedSectionIndex,
        selectedIndex: nextSelectedIndex
      });
    }
    else if (this.isInTokenSelection(tokens.length)) {
      this.setState({
        tokenSelectionDirection: DIRECTION_NONE,
        tokenSelectionStart: tokens.length,
        tokenSelectionEnd: tokens.length + 1,
      });
    }
  }

  onLeftRight(event) {
    const {
      selectionStart,
      selectionEnd,
      selectionDirection
    } = this.refs.input;

    const hasCaret = (selectionStart === selectionEnd);

    let {
      tokens,
      tokenSelectionDirection,
      tokenSelectionEnd,
      tokenSelectionStart
    } = this.state;

    const hasTokenCaret = (tokenSelectionStart === tokenSelectionEnd)

    // TODO: Manage RTL languages

    const keyDirection =
      (event.which === RIGHT) ? DIRECTION_FORWARD :
      (event.which === LEFT) ? DIRECTION_BACKWARD :
      DIRECTION_NONE;

    const selectToHome = isSelectToHome(event);
    const selectToEnd = isSelectToEnd(event);

    if (!selectToHome && !selectToEnd && tokenSelectionStart >= tokens.length) {
      // The text field is focused

      if (selectionStart > 0) {
        // We’re moving inside the text field
        return;
      }

      if (selectionDirection === DIRECTION_FORWARD) {
        // The 'caret' is on the opposite side
        return;
      }

      if (selectionDirection === DIRECTION_BACKWARD && !event.shiftKey) {
        // We do have a selection but we’re exiting it on the left
        return;
      }

      if (keyDirection === DIRECTION_FORWARD) {
        // We are moving on the opposite side
        return;
      }
    }

    if (!selectToHome && !selectToEnd) {
      if (tokenSelectionEnd <= tokens.length) {
        event.preventDefault();
      }
    }

    if (selectToHome) {
      tokenSelectionStart = 0;

      if ((tokenSelectionEnd - tokenSelectionStart) > 1) {
        if (tokenSelectionDirection === DIRECTION_NONE) {
          tokenSelectionDirection = DIRECTION_BACKWARD;
        }
      }
    }
    else if (selectToEnd) {
      tokenSelectionEnd = tokens.length + 1;

      this.refs.input.setSelectionRange(
        selectionStart, this.refs.input.value.length);

      if (tokenSelectionStart < tokens.length) {
        this.refs.input.setSelectionRange(
          0, this.refs.input.value.length);
      }

      if ((tokenSelectionEnd - tokenSelectionStart) > 1) {
        if (tokenSelectionDirection === DIRECTION_NONE) {
          tokenSelectionDirection = DIRECTION_FORWARD;
        }
      }
    }
    else if (!event.shiftKey) {
      if (tokenSelectionDirection === DIRECTION_NONE) {
        if (keyDirection === DIRECTION_FORWARD) {
          tokenSelectionStart += 1;
          tokenSelectionEnd += 1;
        }
        else if (keyDirection === DIRECTION_BACKWARD) {
          tokenSelectionStart -= 1;
          tokenSelectionEnd -= 1;
        }
      }
      else if (keyDirection === DIRECTION_FORWARD) {
        tokenSelectionStart = tokenSelectionEnd - 1;
      }
      else if (keyDirection === DIRECTION_BACKWARD) {
        tokenSelectionEnd = tokenSelectionStart + 1;
      }

      tokenSelectionDirection = DIRECTION_NONE;

      if (tokenSelectionStart <= tokens.length) {
        this.refs.input.setSelectionRange(0, 0);
      }
    }
    else {
      const initialIsInputInSelection = (tokenSelectionEnd > tokens.length);

      if (tokenSelectionDirection === DIRECTION_NONE) {
        tokenSelectionDirection = keyDirection;
      }

      const increment = (tokenSelectionDirection === keyDirection) ? 1 : -1;

      if (tokenSelectionDirection === DIRECTION_FORWARD) {
        tokenSelectionEnd += increment;
      }
      else if (tokenSelectionDirection === DIRECTION_BACKWARD) {
        tokenSelectionStart -= increment;
      }

      if ((tokenSelectionEnd - tokenSelectionStart) <= 1) {
        tokenSelectionDirection = DIRECTION_NONE;
      }

      if (tokenSelectionEnd > tokens.length) {
        if (keyDirection !== tokenSelectionDirection) {
          event.preventDefault();
        }

        if (!initialIsInputInSelection && (keyDirection === DIRECTION_FORWARD)) {
          this.refs.input.setSelectionRange(0, 1);
        }
      }
    }

    tokenSelectionStart = Math.max(0, tokenSelectionStart);
    tokenSelectionEnd = Math.max(tokenSelectionStart + 1, tokenSelectionEnd);

    tokenSelectionEnd = Math.min(tokens.length + 1, tokenSelectionEnd);
    tokenSelectionStart = Math.min(tokenSelectionEnd - 1, tokenSelectionStart);

    this.setState({
      tokenSelectionDirection,
      tokenSelectionEnd,
      tokenSelectionStart
    });
  }

  onBackspace(event) {
    const { tokens, tokenSelectionStart, tokenSelectionEnd } = this.state;

    this.setState({
      tokens: [
        ...tokens.slice(0, tokenSelectionStart),
        ...tokens.slice(tokenSelectionEnd)
      ]
    });

    const { selectionStart, selectionEnd } = this.refs.input;

    const caretIsAtStart = (selectionStart === 0) && (selectionEnd === 0);

    if (this.isInTokenSelection(tokens.length) && caretIsAtStart) {
      this.setState({
        tokenSelectionDirection: DIRECTION_NONE,
        tokenSelectionStart: tokenSelectionStart - 1,
        tokenSelectionEnd: tokenSelectionStart
      });
    }
    else {
      this.setState({
        tokenSelectionDirection: DIRECTION_NONE,
        tokenSelectionStart: tokenSelectionStart,
        tokenSelectionEnd: tokenSelectionStart + 1
      });
    }

    if (event.target !== this.refs.input) {
      event.preventDefault();
    }
  }

  onEnter(event) {
    const {
      showDropDown,
      selectedSectionIndex,
      selectedIndex
    } = this.state;

    const { dropdownSections } = this.props;

    if (showDropDown) {
      this.setState({
        showDropDown: false
      });

      if (!dropdownSections) {
        return;
      }

      const section = dropdownSections[selectedSectionIndex];

      if (!section) {
        return;
      }

      const suggestion = section.suggestions[selectedIndex];

      if (!suggestion) {
        return;
      }

      this.addToken(suggestion.result);
    }

    /*const { tokens, searchText } = this.state;

    if (!searchText.trim()) {
      return;
    }

    const nextTokens = [
      ...tokens,
      { label: searchText } // his.props.getToken(searchText)
    ];

    this.setState({
      searchText: '',
      tokens: nextTokens,
      tokenSelectionDirection: DIRECTION_NONE,
      tokenSelectionStart: tokens.length + 1,
      tokenSelectionEnd: tokens.length + 2
    });

    this.props.onChange({ searchText: '', tokens: nextTokens });*/
  }

  addToken(token) {
    const { tokens } = this.state;

    const nextTokens = tokens.concat([ token ]);

    this.setState({
      searchText: '',
      showDropDown: false,
      tokens: nextTokens,
      tokenSelectionDirection: DIRECTION_NONE,
      tokenSelectionStart: tokens.length + 1,
      tokenSelectionEnd: tokens.length + 2
    });

    this.props.onChange({
      searchText: '',
      tokens: nextTokens
    });
  }

  updateToken(token, index) {
    const { tokens } = this.state;

    this.setState({
      tokens: [
        ...tokens.slice(0, index),
        token,
        ...tokens.slice(index + 1)
      ]
    })
  }

};

FacetedTokenInput.defaultProps = DEFAULT_PROPS;
<|endoftext|>


<commit_before>import ChatRoom, { parser } from './ChatRoom';
import { $pres } from 'strophe.js';
import XMPPEvents from '../../service/xmpp/XMPPEvents';

// This rule makes creating the xml elements take up way more
// space than necessary.
/* eslint-disable newline-per-chained-call */
// These rules makes the xml strings harder to read
/* eslint-disable operator-linebreak, max-len */

describe('ChatRoom', () => {
    describe('packet2JSON', () => {
        let nodes = [];

        beforeEach(() => {
            nodes = [];
        });

        it('translates attributes correctly', () => {
            const p = $pres({
                to: 'tojid',
                from: 'fromjid'
            })
            .c('fake-with-attr', {
                fakeAttr1: 'attrValue1',
                fakeAttr2: 'attrValue2'
            }).up();

            parser.packet2JSON(p.tree(), nodes);
            expect(nodes.length).toBe(1);

            const fakeWithAttr = nodes
                .find(n => n.tagName === 'fake-with-attr');

            expect(fakeWithAttr).toBeTruthy();
            expect(Object.keys(fakeWithAttr.attributes).length).toEqual(2);
            expect(fakeWithAttr.attributes.fakeAttr1).toBeTruthy();
            expect(fakeWithAttr.attributes.fakeAttr1).toEqual('attrValue1');
            expect(fakeWithAttr.attributes.fakeAttr2).toBeTruthy();
            expect(fakeWithAttr.attributes.fakeAttr2).toEqual('attrValue2');
            expect(fakeWithAttr.children.length).toEqual(0);
            expect(fakeWithAttr.value).toBeFalsy();
        });

        it('translates element text correctly', () => {
            const p = $pres({
                to: 'tojid',
                from: 'fromjid'
            })
            .c('element-name').t('element-name-text').up();

            parser.packet2JSON(p.tree(), nodes);

            expect(nodes.length).toBe(1);
            const elem = nodes.find(n => n.tagName === 'element-name');

            expect(elem).toBeTruthy();
            expect(Object.keys(elem.attributes).length).toEqual(0);
            expect(elem.children.length).toEqual(0);
            expect(elem.value).toEqual('element-name-text');
        });

        it('translates elements with children correctly', () => {
            const p = $pres({
                to: 'tojid',
                from: 'fromjid'
            })
            .c('identity')
                .c('user')
                    .c('id').t('id-text').up()
                    .c('name').t('name-text').up()
                    .c('avatar').t('avatar-text').up()
                .up()
                .c('group').t('group-text').up()
            .up();

            parser.packet2JSON(p.tree(), nodes);

            const identity = nodes.find(n => n.tagName === 'identity');

            expect(identity).toBeTruthy();
            expect(Object.keys(identity.attributes).length).toEqual(0);
            expect(identity.children.length).toEqual(2);
            {
                const user = identity.children
                    .find(n => n.tagName === 'user');

                expect(user).toBeTruthy();
                expect(Object.keys(user.attributes).length).toEqual(0);
                expect(user.children.length).toEqual(3);
                {
                    const id = user.children
                        .find(n => n.tagName === 'id');

                    expect(id).toBeTruthy();
                    expect(Object.keys(id.attributes).length).toEqual(0);
                    expect(id.children.length).toEqual(0);
                    expect(id.value).toEqual('id-text');
                }
                {
                    const name = user.children
                        .find(n => n.tagName === 'name');

                    expect(name).toBeTruthy();
                    expect(Object.keys(name.attributes).length).toEqual(0);
                    expect(name.children.length).toEqual(0);
                    expect(name.value).toEqual('name-text');
                }
                {
                    const avatar = user.children
                        .find(n => n.tagName === 'avatar');

                    expect(avatar).toBeTruthy();
                    expect(Object.keys(avatar.attributes).length).toEqual(0);
                    expect(avatar.children.length).toEqual(0);
                    expect(avatar.value).toEqual('avatar-text');
                }
                expect(user.value).toBeFalsy();
            }
            {
                const group = identity.children
                    .find(n => n.tagName === 'group');

                expect(group).toBeTruthy();
                expect(Object.keys(group.attributes).length).toEqual(0);
                expect(group.children.length).toEqual(0);
                expect(group.value).toEqual('group-text');
            }
            expect(identity.value).toBeFalsy();
        });
    });

    describe('onPresence', () => {
        let room;
        let emitterSpy;

        beforeEach(() => {
            const xmpp = {
                options: {}
            };

            room = new ChatRoom(
                {} /* connection */,
                'jid',
                'password',
                xmpp,
                {} /* options */);
            emitterSpy = spyOn(room.eventEmitter, 'emit');
        });
        it('parses status correctly', () => {
            const presStr = '' +
                '<presence to="tojid" from="fromjid">' +
                    '<status>status-text</status>' +
                '</presence>';
            const pres = new DOMParser().parseFromString(presStr, 'text/xml').documentElement;

            room.onPresence(pres);
            expect(emitterSpy.calls.count()).toEqual(2);
            expect(emitterSpy.calls.argsFor(0)).toEqual([
                XMPPEvents.PRESENCE_RECEIVED,
                jasmine.any(Object)
            ]);
            expect(emitterSpy).toHaveBeenCalledWith(
                XMPPEvents.MUC_MEMBER_JOINED,
                'fromjid',
                undefined, // nick
                undefined, // role
                undefined, // isHiddenDomain
                undefined, // statsID
                'status-text',
                undefined,
                undefined);
        });

        it('parses muc user item correctly', () => {
            const presStr = '' +
                '<presence to="tojid" from="fromjid">' +
                    '<x xmlns="http://jabber.org/protocol/muc#user">' +
                        '<item jid="jid=attr" affiliation="affiliation-attr" role="role-attr"/>' +
                    '</x>' +
                '</presence>';
            const pres = new DOMParser().parseFromString(presStr, 'text/xml').documentElement;

            room.onPresence(pres);
            expect(emitterSpy.calls.count()).toEqual(2);
            expect(emitterSpy.calls.argsFor(0)).toEqual([
                XMPPEvents.PRESENCE_RECEIVED,
                jasmine.any(Object)
            ]);
            expect(emitterSpy).toHaveBeenCalledWith(
                XMPPEvents.MUC_MEMBER_JOINED,
                'fromjid',
                undefined, // nick
                'role-attr', // role
                jasmine.any(Boolean), // isHiddenDomain
                undefined, // statsID
                undefined,
                undefined,
                undefined);
        });

        it('parses identity correctly', () => {
            const presStr = '' +
                '<presence to="tojid" from="fromjid">' +
                    '<status>status-text</status>' +
                    '<identity>' +
                        '<user>' +
                            '<id>id-text</id>' +
                            '<name>name-text</name>' +
                            '<avatar>avatar-text</avatar>' +
                        '</user>' +
                        '<group>group-text</group>' +
                    '</identity>' +
                '</presence>';
            const pres = new DOMParser().parseFromString(presStr, 'text/xml').documentElement;

            const expectedIdentity = {
                user: {
                    id: 'id-text',
                    name: 'name-text',
                    avatar: 'avatar-text'
                },
                group: 'group-text'
            };

            room.onPresence(pres);
            expect(emitterSpy.calls.count()).toEqual(2);
            expect(emitterSpy.calls.argsFor(0)).toEqual([
                XMPPEvents.PRESENCE_RECEIVED,
                jasmine.any(Object)
            ]);
            expect(emitterSpy).toHaveBeenCalledWith(
                XMPPEvents.MUC_MEMBER_JOINED,
                'fromjid',
                undefined, // nick
                undefined, // role
                undefined, // isHiddenDomain
                undefined, // statsID
                'status-text',
                expectedIdentity,
                undefined);
        });

        it('parses bot correctly', () => {
            const expectedBotType = 'some_bot_type';
            const presStr = '' +
                '<presence to="tojid" from="fromjid">' +
                    '<status>status-text</status>' +
                    `<bot type="${expectedBotType}"/>` +
                '</presence>';
            const pres = new DOMParser().parseFromString(presStr, 'text/xml').documentElement;

            room.onPresence(pres);
            expect(emitterSpy.calls.count()).toEqual(2);
            expect(emitterSpy.calls.argsFor(0)).toEqual([
                XMPPEvents.PRESENCE_RECEIVED,
                jasmine.any(Object)
            ]);
            expect(emitterSpy).toHaveBeenCalledWith(
                XMPPEvents.MUC_MEMBER_JOINED,
                'fromjid',
                undefined, // nick
                undefined, // role
                undefined, // isHiddenDomain
                undefined, // statsID
                'status-text',
                undefined,
                expectedBotType);
        });

    });
});

<commit_msg>fix(ChatRoom.spec): broken by 0f4cd357 "ignore undefined from"<commit_after>import ChatRoom, { parser } from './ChatRoom';
import { $pres } from 'strophe.js';
import XMPPEvents from '../../service/xmpp/XMPPEvents';

// This rule makes creating the xml elements take up way more
// space than necessary.
/* eslint-disable newline-per-chained-call */
// These rules makes the xml strings harder to read
/* eslint-disable operator-linebreak, max-len */

describe('ChatRoom', () => {
    describe('packet2JSON', () => {
        let nodes = [];

        beforeEach(() => {
            nodes = [];
        });

        it('translates attributes correctly', () => {
            const p = $pres({
                to: 'tojid',
                from: 'fromjid'
            })
            .c('fake-with-attr', {
                fakeAttr1: 'attrValue1',
                fakeAttr2: 'attrValue2'
            }).up();

            parser.packet2JSON(p.tree(), nodes);
            expect(nodes.length).toBe(1);

            const fakeWithAttr = nodes
                .find(n => n.tagName === 'fake-with-attr');

            expect(fakeWithAttr).toBeTruthy();
            expect(Object.keys(fakeWithAttr.attributes).length).toEqual(2);
            expect(fakeWithAttr.attributes.fakeAttr1).toBeTruthy();
            expect(fakeWithAttr.attributes.fakeAttr1).toEqual('attrValue1');
            expect(fakeWithAttr.attributes.fakeAttr2).toBeTruthy();
            expect(fakeWithAttr.attributes.fakeAttr2).toEqual('attrValue2');
            expect(fakeWithAttr.children.length).toEqual(0);
            expect(fakeWithAttr.value).toBeFalsy();
        });

        it('translates element text correctly', () => {
            const p = $pres({
                to: 'tojid',
                from: 'fromjid'
            })
            .c('element-name').t('element-name-text').up();

            parser.packet2JSON(p.tree(), nodes);

            expect(nodes.length).toBe(1);
            const elem = nodes.find(n => n.tagName === 'element-name');

            expect(elem).toBeTruthy();
            expect(Object.keys(elem.attributes).length).toEqual(0);
            expect(elem.children.length).toEqual(0);
            expect(elem.value).toEqual('element-name-text');
        });

        it('translates elements with children correctly', () => {
            const p = $pres({
                to: 'tojid',
                from: 'fromjid'
            })
            .c('identity')
                .c('user')
                    .c('id').t('id-text').up()
                    .c('name').t('name-text').up()
                    .c('avatar').t('avatar-text').up()
                .up()
                .c('group').t('group-text').up()
            .up();

            parser.packet2JSON(p.tree(), nodes);

            const identity = nodes.find(n => n.tagName === 'identity');

            expect(identity).toBeTruthy();
            expect(Object.keys(identity.attributes).length).toEqual(0);
            expect(identity.children.length).toEqual(2);
            {
                const user = identity.children
                    .find(n => n.tagName === 'user');

                expect(user).toBeTruthy();
                expect(Object.keys(user.attributes).length).toEqual(0);
                expect(user.children.length).toEqual(3);
                {
                    const id = user.children
                        .find(n => n.tagName === 'id');

                    expect(id).toBeTruthy();
                    expect(Object.keys(id.attributes).length).toEqual(0);
                    expect(id.children.length).toEqual(0);
                    expect(id.value).toEqual('id-text');
                }
                {
                    const name = user.children
                        .find(n => n.tagName === 'name');

                    expect(name).toBeTruthy();
                    expect(Object.keys(name.attributes).length).toEqual(0);
                    expect(name.children.length).toEqual(0);
                    expect(name.value).toEqual('name-text');
                }
                {
                    const avatar = user.children
                        .find(n => n.tagName === 'avatar');

                    expect(avatar).toBeTruthy();
                    expect(Object.keys(avatar.attributes).length).toEqual(0);
                    expect(avatar.children.length).toEqual(0);
                    expect(avatar.value).toEqual('avatar-text');
                }
                expect(user.value).toBeFalsy();
            }
            {
                const group = identity.children
                    .find(n => n.tagName === 'group');

                expect(group).toBeTruthy();
                expect(Object.keys(group.attributes).length).toEqual(0);
                expect(group.children.length).toEqual(0);
                expect(group.value).toEqual('group-text');
            }
            expect(identity.value).toBeFalsy();
        });
    });

    describe('onPresence', () => {
        let room;
        let emitterSpy;

        beforeEach(() => {
            const xmpp = {
                options: {}
            };

            room = new ChatRoom(
                {} /* connection */,
                'jid',
                'password',
                xmpp,
                {} /* options */);
            emitterSpy = spyOn(room.eventEmitter, 'emit');
        });
        it('parses status correctly', () => {
            const presStr = '' +
                '<presence to="tojid" from="fromjid">' +
                    '<x xmlns=\'http://jabber.org/protocol/muc#user\'>' +
                        '<item jid=\'fulljid\'/>' +
                    '</x>' +
                    '<status>status-text</status>' +
                '</presence>';
            const pres = new DOMParser().parseFromString(presStr, 'text/xml').documentElement;

            room.onPresence(pres);
            expect(emitterSpy.calls.count()).toEqual(2);
            expect(emitterSpy.calls.argsFor(0)).toEqual([
                XMPPEvents.PRESENCE_RECEIVED,
                jasmine.any(Object)
            ]);
            expect(emitterSpy.calls.argsFor(1)).toEqual([
                XMPPEvents.MUC_MEMBER_JOINED,
                'fromjid',
                undefined, // nick
                null, // role
                false, // isHiddenDomain
                undefined, // statsID
                'status-text',
                undefined,
                undefined
            ]);
        });

        it('parses muc user item correctly', () => {
            const presStr = '' +
                '<presence to="tojid" from="fromjid">' +
                    '<x xmlns="http://jabber.org/protocol/muc#user">' +
                        '<item jid="jid=attr" affiliation="affiliation-attr" role="role-attr"/>' +
                    '</x>' +
                '</presence>';
            const pres = new DOMParser().parseFromString(presStr, 'text/xml').documentElement;

            room.onPresence(pres);
            expect(emitterSpy.calls.count()).toEqual(2);
            expect(emitterSpy.calls.argsFor(0)).toEqual([
                XMPPEvents.PRESENCE_RECEIVED,
                jasmine.any(Object)
            ]);
            expect(emitterSpy).toHaveBeenCalledWith(
                XMPPEvents.MUC_MEMBER_JOINED,
                'fromjid',
                undefined, // nick
                'role-attr', // role
                jasmine.any(Boolean), // isHiddenDomain
                undefined, // statsID
                undefined,
                undefined,
                undefined);
        });

        it('parses identity correctly', () => {
            const presStr = '' +
                '<presence to="tojid" from="fromjid">' +
                    '<x xmlns=\'http://jabber.org/protocol/muc#user\'>' +
                        '<item jid=\'fulljid\'/>' +
                    '</x>' +
                    '<status>status-text</status>' +
                    '<identity>' +
                        '<user>' +
                            '<id>id-text</id>' +
                            '<name>name-text</name>' +
                            '<avatar>avatar-text</avatar>' +
                        '</user>' +
                        '<group>group-text</group>' +
                    '</identity>' +
                '</presence>';
            const pres = new DOMParser().parseFromString(presStr, 'text/xml').documentElement;

            const expectedIdentity = {
                user: {
                    id: 'id-text',
                    name: 'name-text',
                    avatar: 'avatar-text'
                },
                group: 'group-text'
            };

            room.onPresence(pres);
            expect(emitterSpy.calls.count()).toEqual(2);
            expect(emitterSpy.calls.argsFor(0)).toEqual([
                XMPPEvents.PRESENCE_RECEIVED,
                jasmine.any(Object)
            ]);
            expect(emitterSpy.calls.argsFor(1)).toEqual([
                XMPPEvents.MUC_MEMBER_JOINED,
                'fromjid',
                undefined, // nick
                null, // role
                false, // isHiddenDomain
                undefined, // statsID
                'status-text',
                expectedIdentity,
                undefined
            ]);
        });

        it('parses bot correctly', () => {
            const expectedBotType = 'some_bot_type';
            const presStr = '' +
                '<presence to="tojid" from="fromjid">' +
                    '<x xmlns=\'http://jabber.org/protocol/muc#user\'>' +
                        '<item jid=\'fulljid\'/>' +
                    '</x>' +
                    '<status>status-text</status>' +
                    `<bot type="${expectedBotType}"/>` +
                '</presence>';
            const pres = new DOMParser().parseFromString(presStr, 'text/xml').documentElement;

            room.onPresence(pres);
            expect(emitterSpy.calls.count()).toEqual(2);
            expect(emitterSpy.calls.argsFor(0)).toEqual([
                XMPPEvents.PRESENCE_RECEIVED,
                jasmine.any(Object)
            ]);
            expect(emitterSpy.calls.argsFor(1)).toEqual([
                XMPPEvents.MUC_MEMBER_JOINED,
                'fromjid',
                undefined, // nick
                null, // role
                false, // isHiddenDomain
                undefined, // statsID
                'status-text',
                undefined,
                expectedBotType
            ]);
        });

    });
});

<|endoftext|>


<commit_before>/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */
/*global require, exports */

////////////////////////////////////////////////////////////////////////////////
/// @brief AQL user functions management
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2012 triagens GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///     http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Jan Steemann
/// @author Copyright 2012, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

var internal = require("internal");
var arangodb = require("org/arangodb");

var db = arangodb.db;
var ArangoError = arangodb.ArangoError;

// -----------------------------------------------------------------------------
// --SECTION--                               module "org/arangodb/aql/functions"
// -----------------------------------------------------------------------------

// -----------------------------------------------------------------------------
// --SECTION--                                                 private functions
// -----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
/// @addtogroup Ahuacatl
/// @{
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
/// @brief return the _aqlfunctions collection
////////////////////////////////////////////////////////////////////////////////

var getStorage = function () {
  var functions = db._collection("_aqlfunctions");

  if (functions === null) {
    var err = new ArangoError();
    err.errorNum = arangodb.errors.ERROR_ARANGO_COLLECTION_NOT_FOUND.code;
    err.errorMessage = "collection '_aqlfunctions' not found";

    throw err;
  }

  return functions;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief apply a prefix filter on the functions
////////////////////////////////////////////////////////////////////////////////

var getFiltered = function (group) {
  var result = [ ];

  if (group !== null && group !== undefined && group.length > 0) {
    var prefix = group.toUpperCase();

    if (group.substr(group.length - 1, 1) !== ':') {
      prefix += ':';
    }

    getStorage().toArray().forEach(function (f) {
      if (f.name.toUpperCase().substr(0, prefix.length) === prefix) {
        result.push(f);
      }
    });
  }
  else {
    result = getStorage().toArray();
  }

  return result;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief validate a function name
////////////////////////////////////////////////////////////////////////////////

var validateName = function (name) {
  if (typeof name !== 'string' ||
      ! name.match(/^[a-zA-Z0-9_]+(:[a-zA-Z0-9_]+)+$/) ||
      name.substr(0, 1) === "_") {
    var err = new ArangoError();
    err.errorNum = arangodb.errors.ERROR_QUERY_FUNCTION_INVALID_NAME.code;
    err.errorMessage = arangodb.errors.ERROR_QUERY_FUNCTION_INVALID_NAME.message;

    throw err;
  }
};

////////////////////////////////////////////////////////////////////////////////
/// @brief validate user function code
////////////////////////////////////////////////////////////////////////////////

var stringifyFunction = function (code, name) {
  if (typeof code === 'function') {
    code = String(code);
  }

  if (typeof code === 'string') {
    code = "(" + code + ")";

    if (! internal.parse) {
      // no parsing possible. assume always valid
      return code;
    }

    try {
      if (internal.parse(code, name)) {
        // parsing successful
        return code;
      }
    }
    catch (e) {
    }
  }

  // fall-through intentional

  var err = new ArangoError();
  err.errorNum = arangodb.errors.ERROR_QUERY_FUNCTION_INVALID_CODE.code;
  err.errorMessage = arangodb.errors.ERROR_QUERY_FUNCTION_INVALID_CODE.message;

  throw err;
};

////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////

// -----------------------------------------------------------------------------
// --SECTION--                                                  public functions
// -----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
/// @addtogroup Ahuacatl
/// @{
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_aqlfunctions_unregister
/// @brief delete an existing AQL user function
///
/// @FUN{aqlfunctions.unregister(@FA{name})}
///
/// Unregisters an existing AQL user function, identified by the fully qualified
/// function name.
///
/// Trying to unregister a function that does not exist will result in an
/// exception.
///
/// @EXAMPLES
///
/// @code
/// arangosh> require("org/arangodb/aql/functions").unregister("myfunctions:temperature:celsiustofahrenheit");
/// @endcode
////////////////////////////////////////////////////////////////////////////////

var unregisterFunction = function (name) {
  var func = null;

  validateName(name);

  try {
    func = getStorage().document(name.toUpperCase());
  }
  catch (err1) {
  }

  if (func === null) {
    var err = new ArangoError();
    err.errorNum = arangodb.errors.ERROR_QUERY_FUNCTION_NOT_FOUND.code;
    err.errorMessage = internal.sprintf(arangodb.errors.ERROR_QUERY_FUNCTION_NOT_FOUND.message, name);

    throw err;
  }

  getStorage().remove(func._id);
  internal.reloadAqlFunctions();

  return true;
};

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_aqlfunctions_unregister_group
/// @brief delete a group of AQL user functions
///
/// @FUN{aqlfunctions.unregisterGroup(@FA{prefix})}
///
/// Unregisters a group of AQL user function, identified by a common function
/// group prefix.
///
/// This will return the number of functions unregistered.
///
/// @EXAMPLES
///
/// @code
/// arangosh> require("org/arangodb/aql/functions").unregisterGroup("myfunctions:temperature");
///
/// arangosh> require("org/arangodb/aql/functions").unregisterGroup("myfunctions");
/// @endcode
////////////////////////////////////////////////////////////////////////////////

var unregisterFunctionsGroup = function (group) {
  if (group.length === 0) {
    var err = new ArangoError();
    err.errorNum = arangodb.errors.ERROR_BAD_PARAMETER.code;
    err.errorMessage = arangodb.errors.ERROR_BAD_PARAMETER.message;
  }

  var deleted = 0;

  getFiltered(group).forEach(function (f) {
    getStorage().remove(f._id);
    deleted++;
  });

  if (deleted > 0) {
    internal.reloadAqlFunctions();
  }

  return deleted;
};

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_aqlfunctions_register
/// @brief register an AQL user function
///
/// @FUN{aqlfunctions.register(@FA{name}, @FA{code}, @FA{isDeterministic})}
///
/// Registers an AQL user function, identified by a fully qualified function
/// name. The function code in @FA{code} must be specified as a Javascript
/// function or a string representation of a Javascript function.
///
/// If a function identified by @FA{name} already exists, the previous function
/// definition will be updated.
///
/// The @FA{isDeterministic} attribute can be used to specify whether the
/// function results are fully deterministic (i.e. depend solely on the input
/// and are the same for repeated calls with the same input values). It is not
/// used at the moment but may be used for optimisations later.
///
/// @EXAMPLES
///
/// @code
/// arangosh> require("org/arangodb/aql/functions").register("myfunctions:temperature:celsiustofahrenheit",
///                   function (celsius) {
///                     return celsius * 1.8 + 32;
///                   });
/// @endcode
////////////////////////////////////////////////////////////////////////////////

var registerFunction = function (name, code, isDeterministic) {
  // validate input
  validateName(name);

  code = stringifyFunction(code, name);

  var testCode = "(function() { var callback = " + code + "; return callback; })()";

  try {
    var res = internal.executeScript(testCode, undefined, "(user function " + name + ")");
  }
  catch (err1) {
    var err = new ArangoError();
    err.errorNum = arangodb.errors.ERROR_QUERY_FUNCTION_INVALID_CODE.code;
    err.errorMessage = arangodb.errors.ERROR_QUERY_FUNCTION_INVALID_CODE.message;
  }

  var exists = false;

  try {
    unregisterFunction(name);
    exists = true;
  }
  catch (err2) {
  }

  var data = {
    _key: name.toUpperCase(),
    name: name,
    code: code,
    isDeterministic: isDeterministic || false
  };

  getStorage().save(data);
  internal.reloadAqlFunctions();

  return exists;
};

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_aqlfunctions_toArray
/// @brief list all AQL user functions
///
/// @FUN{aqlfunctions.toArray()}
///
/// Returns all previously registered AQL user functions, with their fully
/// qualified names and function code.
///
/// The result may optionally be restricted to a specified group of functions
/// by specifying a group prefix:
///
/// @FUN{aqlfunctions.toArray(@FA{prefix})}
///
/// @EXAMPLES
///
/// To list all available user functions:
///
/// @code
/// arangosh> require("org/arangodb/aql/functions").toArray();
/// @endcode
///
/// To list all available user functions in the `myfunctions` namespace:
///
/// @code
/// arangosh> require("org/arangodb/aql/functions").toArray("myfunctions");
/// @endcode
///
/// To list all available user functions in the `myfunctions:temperature` namespace:
///
/// @code
/// arangosh> require("org/arangodb/aql/functions").toArray("myfunctions:temperature");
/// @endcode
////////////////////////////////////////////////////////////////////////////////

var toArrayFunctions = function (group) {
  var result = [ ];

  getFiltered(group).forEach(function (f) {
    result.push({ name: f.name, code: f.code.substr(1, f.code.length - 2) });
  });

  return result;
};

////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////

// -----------------------------------------------------------------------------
// --SECTION--                                                    module exports
// -----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
/// @addtogroup Ahuacatl
/// @{
////////////////////////////////////////////////////////////////////////////////

exports.unregister      = unregisterFunction;
exports.unregisterGroup = unregisterFunctionsGroup;
exports.register        = registerFunction;
exports.toArray         = toArrayFunctions;

////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////

// -----------------------------------------------------------------------------
// --SECTION--                                                       END-OF-FILE
// -----------------------------------------------------------------------------

// Local Variables:
// mode: outline-minor
// outline-regexp: "/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\|/\\*jslint"
// End:

<commit_msg>jslint<commit_after>/*jslint indent: 2, nomen: true, maxlen: 120, sloppy: true, vars: true, white: true, plusplus: true */
/*global require, exports */

////////////////////////////////////////////////////////////////////////////////
/// @brief AQL user functions management
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2012 triagens GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///     http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Jan Steemann
/// @author Copyright 2012, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

var internal = require("internal");
var arangodb = require("org/arangodb");

var db = arangodb.db;
var ArangoError = arangodb.ArangoError;

// -----------------------------------------------------------------------------
// --SECTION--                               module "org/arangodb/aql/functions"
// -----------------------------------------------------------------------------

// -----------------------------------------------------------------------------
// --SECTION--                                                 private functions
// -----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
/// @addtogroup Ahuacatl
/// @{
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
/// @brief return the _aqlfunctions collection
////////////////////////////////////////////////////////////////////////////////

var getStorage = function () {
  var functions = db._collection("_aqlfunctions");

  if (functions === null) {
    var err = new ArangoError();
    err.errorNum = arangodb.errors.ERROR_ARANGO_COLLECTION_NOT_FOUND.code;
    err.errorMessage = "collection '_aqlfunctions' not found";

    throw err;
  }

  return functions;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief apply a prefix filter on the functions
////////////////////////////////////////////////////////////////////////////////

var getFiltered = function (group) {
  var result = [ ];

  if (group !== null && group !== undefined && group.length > 0) {
    var prefix = group.toUpperCase();

    if (group.substr(group.length - 1, 1) !== ':') {
      prefix += ':';
    }

    getStorage().toArray().forEach(function (f) {
      if (f.name.toUpperCase().substr(0, prefix.length) === prefix) {
        result.push(f);
      }
    });
  }
  else {
    result = getStorage().toArray();
  }

  return result;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief validate a function name
////////////////////////////////////////////////////////////////////////////////

var validateName = function (name) {
  if (typeof name !== 'string' ||
      ! name.match(/^[a-zA-Z0-9_]+(:[a-zA-Z0-9_]+)+$/) ||
      name.substr(0, 1) === "_") {
    var err = new ArangoError();
    err.errorNum = arangodb.errors.ERROR_QUERY_FUNCTION_INVALID_NAME.code;
    err.errorMessage = arangodb.errors.ERROR_QUERY_FUNCTION_INVALID_NAME.message;

    throw err;
  }
};

////////////////////////////////////////////////////////////////////////////////
/// @brief validate user function code
////////////////////////////////////////////////////////////////////////////////

var stringifyFunction = function (code, name) {
  if (typeof code === 'function') {
    code = String(code);
  }

  if (typeof code === 'string') {
    code = "(" + code + ")";

    if (! internal.parse) {
      // no parsing possible. assume always valid
      return code;
    }

    try {
      if (internal.parse(code, name)) {
        // parsing successful
        return code;
      }
    }
    catch (e) {
    }
  }

  // fall-through intentional

  var err = new ArangoError();
  err.errorNum = arangodb.errors.ERROR_QUERY_FUNCTION_INVALID_CODE.code;
  err.errorMessage = arangodb.errors.ERROR_QUERY_FUNCTION_INVALID_CODE.message;

  throw err;
};

////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////

// -----------------------------------------------------------------------------
// --SECTION--                                                  public functions
// -----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
/// @addtogroup Ahuacatl
/// @{
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_aqlfunctions_unregister
/// @brief delete an existing AQL user function
///
/// @FUN{aqlfunctions.unregister(@FA{name})}
///
/// Unregisters an existing AQL user function, identified by the fully qualified
/// function name.
///
/// Trying to unregister a function that does not exist will result in an
/// exception.
///
/// @EXAMPLES
///
/// @code
/// arangosh> require("org/arangodb/aql/functions").unregister("myfunctions:temperature:celsiustofahrenheit");
/// @endcode
////////////////////////////////////////////////////////////////////////////////

var unregisterFunction = function (name) {
  var func = null;

  validateName(name);

  try {
    func = getStorage().document(name.toUpperCase());
  }
  catch (err1) {
  }

  if (func === null) {
    var err = new ArangoError();
    err.errorNum = arangodb.errors.ERROR_QUERY_FUNCTION_NOT_FOUND.code;
    err.errorMessage = internal.sprintf(arangodb.errors.ERROR_QUERY_FUNCTION_NOT_FOUND.message, name);

    throw err;
  }

  getStorage().remove(func._id);
  internal.reloadAqlFunctions();

  return true;
};

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_aqlfunctions_unregister_group
/// @brief delete a group of AQL user functions
///
/// @FUN{aqlfunctions.unregisterGroup(@FA{prefix})}
///
/// Unregisters a group of AQL user function, identified by a common function
/// group prefix.
///
/// This will return the number of functions unregistered.
///
/// @EXAMPLES
///
/// @code
/// arangosh> require("org/arangodb/aql/functions").unregisterGroup("myfunctions:temperature");
///
/// arangosh> require("org/arangodb/aql/functions").unregisterGroup("myfunctions");
/// @endcode
////////////////////////////////////////////////////////////////////////////////

var unregisterFunctionsGroup = function (group) {
  if (group.length === 0) {
    var err = new ArangoError();
    err.errorNum = arangodb.errors.ERROR_BAD_PARAMETER.code;
    err.errorMessage = arangodb.errors.ERROR_BAD_PARAMETER.message;
  }

  var deleted = 0;

  getFiltered(group).forEach(function (f) {
    getStorage().remove(f._id);
    deleted++;
  });

  if (deleted > 0) {
    internal.reloadAqlFunctions();
  }

  return deleted;
};

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_aqlfunctions_register
/// @brief register an AQL user function
///
/// @FUN{aqlfunctions.register(@FA{name}, @FA{code}, @FA{isDeterministic})}
///
/// Registers an AQL user function, identified by a fully qualified function
/// name. The function code in @FA{code} must be specified as a Javascript
/// function or a string representation of a Javascript function.
///
/// If a function identified by @FA{name} already exists, the previous function
/// definition will be updated.
///
/// The @FA{isDeterministic} attribute can be used to specify whether the
/// function results are fully deterministic (i.e. depend solely on the input
/// and are the same for repeated calls with the same input values). It is not
/// used at the moment but may be used for optimisations later.
///
/// @EXAMPLES
///
/// @code
/// arangosh> require("org/arangodb/aql/functions").register("myfunctions:temperature:celsiustofahrenheit",
///                   function (celsius) {
///                     return celsius * 1.8 + 32;
///                   });
/// @endcode
////////////////////////////////////////////////////////////////////////////////

var registerFunction = function (name, code, isDeterministic) {
  // validate input
  validateName(name);

  code = stringifyFunction(code, name);

  var testCode = "(function() { var callback = " + code + "; return callback; })()";

  try {
    var res = internal.executeScript(testCode, undefined, "(user function " + name + ")");
  }
  catch (err1) {
    var err = new ArangoError();
    err.errorNum = arangodb.errors.ERROR_QUERY_FUNCTION_INVALID_CODE.code;
    err.errorMessage = arangodb.errors.ERROR_QUERY_FUNCTION_INVALID_CODE.message;
  }

  var exists = false;

  try {
    unregisterFunction(name);
    exists = true;
  }
  catch (err2) {
  }

  var data = {
    _key: name.toUpperCase(),
    name: name,
    code: code,
    isDeterministic: isDeterministic || false
  };

  getStorage().save(data);
  internal.reloadAqlFunctions();

  return exists;
};

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_aqlfunctions_toArray
/// @brief list all AQL user functions
///
/// @FUN{aqlfunctions.toArray()}
///
/// Returns all previously registered AQL user functions, with their fully
/// qualified names and function code.
///
/// The result may optionally be restricted to a specified group of functions
/// by specifying a group prefix:
///
/// @FUN{aqlfunctions.toArray(@FA{prefix})}
///
/// @EXAMPLES
///
/// To list all available user functions:
///
/// @code
/// arangosh> require("org/arangodb/aql/functions").toArray();
/// @endcode
///
/// To list all available user functions in the `myfunctions` namespace:
///
/// @code
/// arangosh> require("org/arangodb/aql/functions").toArray("myfunctions");
/// @endcode
///
/// To list all available user functions in the `myfunctions:temperature` namespace:
///
/// @code
/// arangosh> require("org/arangodb/aql/functions").toArray("myfunctions:temperature");
/// @endcode
////////////////////////////////////////////////////////////////////////////////

var toArrayFunctions = function (group) {
  var result = [ ];

  getFiltered(group).forEach(function (f) {
    result.push({ name: f.name, code: f.code.substr(1, f.code.length - 2) });
  });

  return result;
};

////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////

// -----------------------------------------------------------------------------
// --SECTION--                                                    module exports
// -----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
/// @addtogroup Ahuacatl
/// @{
////////////////////////////////////////////////////////////////////////////////

exports.unregister      = unregisterFunction;
exports.unregisterGroup = unregisterFunctionsGroup;
exports.register        = registerFunction;
exports.toArray         = toArrayFunctions;

////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////

// -----------------------------------------------------------------------------
// --SECTION--                                                       END-OF-FILE
// -----------------------------------------------------------------------------

// Local Variables:
// mode: outline-minor
// outline-regexp: "/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\|/\\*jslint"
// End:

<|endoftext|>


<commit_before>queue()
  .defer(d3.json, "/Programmeerproject/data/dataNL.json")
  .defer(d3.json, "/Programmeerproject/data/dataNW.json")
  .defer(d3.json, "/Programmeerproject/data/dataNLPension.json")
  .await(drawLinegraphs);

// Change with dropdownmenu
d3.select('#inds')
  .on("change", function () {
    selectValue = d3.select('select').property('value')
    if (selectValue === "Norwayfund") {
      d3.select("svg#container1").selectAll("rect").remove();
      d3.select("svg#container1").selectAll("text").remove();
      d3.select("svg#container1").selectAll("g").remove();
      d3.select("svg#container1").selectAll("path.line").remove();
      drawChart("svg#container1", "/Programmeerproject/data/CompositionFundNL.csv", "Netherlands");
      queue()
  		.defer(d3.json, "dataNL.json")
  		.defer(d3.json, "dataNW.json")
  		.await(drawLinegraphs);
    }
    else if (selectValue === "Pensionfunds") {
      d3.select("svg#container1").selectAll("rect").remove();
      d3.select("svg#container1").selectAll("text").remove();
      d3.select("svg#container1").selectAll("g").remove();
      d3.select("svg#container1").selectAll("path.line").remove();
      drawChart("svg#container1", "/Programmeerproject/data/CompositionFundNLPension.csv", "Netherlands");
      queue()
      	.defer(d3.json, "dataNLPension.json")
  		.defer(d3.json, "dataNW.json")
  		.await(drawLinegraphs);
    }
})

function drawLinegraphs(error, dataNL, dataNW) {
	if (error) throw error;

	var svg1 = d3.select("svg#container1"),
      margin = {top: 30, right: 70, bottom: 30, left: 50},
      width = +svg1.attr("width") - margin.left - margin.right,
      height = +svg1.attr("height") - margin.top - margin.bottom;

	var svg2 = d3.select("svg#container2"),
      margin = {top: 30, right: 70, bottom: 30, left: 50},
      width = +svg2.attr("width") - margin.left - margin.right,
      height = +svg2.attr("height") - margin.top - margin.bottom;

    var svg3 = d3.select("svg#container3");

	var parseTime = d3.timeParse("%Y")
      bisectDate = d3.bisector(function(d) { return d.Year; }).left;

  	var x = d3.scaleTime().range([0, width]);
  	var y = d3.scaleLinear().range([height, 0]);

  	var line = d3.line()
      .x(function(d) { return x(d.Year); })
      .y(function(d) { return y(d.Marketvalue); });

  	var g1 = svg1.append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    var g2 = svg2.append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // Draw line NL
	dataNL.forEach(function(d) {
	d.Year = parseTime(d.Year);
	d.Marketvalue = +d.Marketvalue;
	});

	x.domain(d3.extent(dataNL, function(d) { return d.Year; }));
	y.domain([0,1000]);

	g1.append("g")
	  .attr("class", "axis axis--x")
	  .attr("transform", "translate(0," + height + ")")

	g1.append("g")
	  .attr("class", "axis axis--y")

	.append("text")
	  .attr("class", "axis-title")
	  .attr("transform", "rotate(-90)")
	  .attr("y", 6)
	  .attr("dy", ".71em")
	  .style("text-anchor", "end")
	  .attr("fill", "#5D6971")

	g1.append("path")
	  .datum(dataNL)
	  .attr("class", "line")
	  .attr("d", line);

	var focus1 = g1.append("g")
	  .attr("class", "focus")
	  .style("display", "none");

	focus1.append("line")
	  .attr("class", "x-hover-line hover-line")
	  .attr("y1", 0)
	  .attr("y2", height);

	focus1.append("line")
	  .attr("class", "y-hover-line hover-line")
	  .attr("x1", width + margin.right)
	  .attr("x2", width + margin.right);

	focus1.append("circle")
	  .attr("r", 7.5);

	focus1.append("text")
	  .attr("x", 15)
		.attr("dy", ".31em");

	svg1.append("rect")
	  .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
	  .attr("class", "overlay")
	  .attr("width", width)
	  .attr("height", height)
		  .on("mouseover", function() {
		  	focus1.style("display", null);
		  	focus2.style("display", null);
		  	tooltip1.style("visibility", "visible");
		  	tooltip2.style("visibility", "visible");
		  })
		  .on("mouseout", function() {
		  	focus1.style("display", "none");
		  	focus2.style("display", "none");
		  	tooltip1.style("visibility", "visible");
		  	tooltip2.style("visibility", "visible");
		  })
		  .on("mousemove", mousemove);

	// Draw line Norway
	dataNW.forEach(function(d) {
	d.Year = parseTime(d.Year);
	d.Marketvalue = +d.Marketvalue;
	});

	x.domain(d3.extent(dataNW, function(d) { return d.Year; }));
	y.domain([0,1000]);

	g2.append("g")
	  .attr("class", "axis axis--x")
	  .attr("transform", "translate(0," + height + ")")

	g2.append("g")
	  .attr("class", "axis axis--y")

	.append("text")
	  .attr("class", "axis-title")
	  .attr("transform", "rotate(-90)")
	  .attr("y", 6)
	  .attr("dy", ".71em")
	  .style("text-anchor", "end")
	  .attr("fill", "#5D6971")

	g2.append("path")
	  .datum(dataNW)
	  .attr("class", "line")
	  .attr("d", line);

	var focus2 = g2.append("g")
	  .attr("class", "focus")
	  .style("display", "none");

	focus2.append("line")
	  .attr("class", "x-hover-line hover-line")
	  .attr("y1", 0)
	  .attr("y2", height);

	focus2.append("line")
	  .attr("class", "y-hover-line hover-line")
	  .attr("x1", width+margin.right)
	  .attr("x2", width+margin.right);

	focus2.append("circle")
	  .attr("r", 7.5);

	focus2.append("text")
	  .attr("x", 30)
	  .attr("dy", ".31em");

	svg2.append("rect")
	  .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
	  .attr("class", "overlay")
	  .attr("width", width)
	  .attr("height", height)
		  .on("mouseover", function() {
		  	focus2.style("display", null);
		  	focus1.style("display", null);
		  	tooltip2.style("visibility", "visible");
		  	tooltip1.style("visibility", "visible");
		  })
		  .on("mouseout", function() {
		  	focus2.style("display", "none");
		  	focus1.style("display", null);
		  	tooltip2.style("visibility", "visible");
		  	tooltip1.style("visibility", "visible");
		  })
		  .on("mousemove", mousemove);

  	var tooltip1 = d3.select("div.chartstart")

	  .style("position", "relative")
	  .style("text-anchor", "left")
	  .style("z-index", "100")
	  .style("visibility", "hidden");

	tooltip1.append("text1")
	  .attr("x", 10)
	  .attr("dy", ".31em");

	var tooltip2 = d3.select("div.chartstart2")

	  .style("position", "relative")
	  .style("text-anchor", "left")
	  .style("z-index", "100")
	  .style("visibility", "hidden");

	tooltip2.append("text2")
	  .attr("x", 10)
	  .attr("dy", ".31em");

	// Create function for mousemove
	function mousemove() {
	var x0 = x.invert(d3.mouse(this)[0]),
	    i = bisectDate(dataNL, x0, 1),
	    d0 = dataNL[i - 1],
	    d1 = dataNL[i],
	    d = x0 - d0.Year > d1.Year - x0 ? d1 : d0;
		d2 = dataNW[d.Year.getYear() - 69];
	focus1.attr("transform", "translate(" + x(d.Year) + "," + y(d.Marketvalue) + ")");
	focus1.select("text").text(function() { return d.Marketvalue; });
	focus1.select(".x-hover-line").attr("y2", height - y(d.Marketvalue));
	focus1.select(".y-hover-line").attr("x2", width + width);
	focus2.attr("transform", "translate(" + x(d2.Year) + "," + y(d2.Marketvalue) + ")");
	focus2.select("text").text(function() { return d2.Marketvalue; });
	focus2.select(".x-hover-line").attr("y2", height - y(d2.Marketvalue));
	focus2.select(".y-hover-line").attr("x2", width + width);
	//tooltip1.attr("transform", "translate(" + x(d.Year) + "," + y(d.Marketvalue) + ")");
	tooltip1.select("text1").text(function() { return "€ " + d.Marketvalue + ".000.000";  });
	//tooltip2.attr("transform", "translate(" + x(d2.Year) + "," + y(d2.Marketvalue) + ")");
	tooltip2.select("text2").text(function() { return "€ " + d2.Marketvalue + ".000.000"; });

	}
}

<commit_msg>Github paths for line graphs<commit_after>queue()
  .defer(d3.json, "/Programmeerproject/data/dataNL.json")
  .defer(d3.json, "/Programmeerproject/data/dataNW.json")
  .defer(d3.json, "/Programmeerproject/data/dataNLPension.json")
  .await(drawLinegraphs);

// Change with dropdownmenu
d3.select('#inds')
  .on("change", function () {
    selectValue = d3.select('select').property('value')
    if (selectValue === "Norwayfund") {
      d3.select("svg#container1").selectAll("rect").remove();
      d3.select("svg#container1").selectAll("text").remove();
      d3.select("svg#container1").selectAll("g").remove();
      d3.select("svg#container1").selectAll("path.line").remove();
      drawChart("svg#container1", "/Programmeerproject/data/CompositionFundNL.csv", "Netherlands");
      queue()
  		.defer(d3.json, "/Programmeerproject/data/dataNL.json")
  		.defer(d3.json, "/Programmeerproject/data/dataNW.json")
  		.await(drawLinegraphs);
    }
    else if (selectValue === "Pensionfunds") {
      d3.select("svg#container1").selectAll("rect").remove();
      d3.select("svg#container1").selectAll("text").remove();
      d3.select("svg#container1").selectAll("g").remove();
      d3.select("svg#container1").selectAll("path.line").remove();
      drawChart("svg#container1", "/Programmeerproject/data/CompositionFundNLPension.csv", "Netherlands");
      queue()
      	.defer(d3.json, "/Programmeerproject/data/dataNL.json")
  		.defer(d3.json, "/Programmeerproject/data/dataNW.json")
  		.await(drawLinegraphs);
    }
})

function drawLinegraphs(error, dataNL, dataNW) {
	if (error) throw error;

	var svg1 = d3.select("svg#container1"),
      margin = {top: 30, right: 70, bottom: 30, left: 50},
      width = +svg1.attr("width") - margin.left - margin.right,
      height = +svg1.attr("height") - margin.top - margin.bottom;

	var svg2 = d3.select("svg#container2"),
      margin = {top: 30, right: 70, bottom: 30, left: 50},
      width = +svg2.attr("width") - margin.left - margin.right,
      height = +svg2.attr("height") - margin.top - margin.bottom;

    var svg3 = d3.select("svg#container3");

	var parseTime = d3.timeParse("%Y")
      bisectDate = d3.bisector(function(d) { return d.Year; }).left;

  	var x = d3.scaleTime().range([0, width]);
  	var y = d3.scaleLinear().range([height, 0]);

  	var line = d3.line()
      .x(function(d) { return x(d.Year); })
      .y(function(d) { return y(d.Marketvalue); });

  	var g1 = svg1.append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    var g2 = svg2.append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // Draw line NL
	dataNL.forEach(function(d) {
	d.Year = parseTime(d.Year);
	d.Marketvalue = +d.Marketvalue;
	});

	x.domain(d3.extent(dataNL, function(d) { return d.Year; }));
	y.domain([0,1000]);

	g1.append("g")
	  .attr("class", "axis axis--x")
	  .attr("transform", "translate(0," + height + ")")

	g1.append("g")
	  .attr("class", "axis axis--y")

	.append("text")
	  .attr("class", "axis-title")
	  .attr("transform", "rotate(-90)")
	  .attr("y", 6)
	  .attr("dy", ".71em")
	  .style("text-anchor", "end")
	  .attr("fill", "#5D6971")

	g1.append("path")
	  .datum(dataNL)
	  .attr("class", "line")
	  .attr("d", line);

	var focus1 = g1.append("g")
	  .attr("class", "focus")
	  .style("display", "none");

	focus1.append("line")
	  .attr("class", "x-hover-line hover-line")
	  .attr("y1", 0)
	  .attr("y2", height);

	focus1.append("line")
	  .attr("class", "y-hover-line hover-line")
	  .attr("x1", width + margin.right)
	  .attr("x2", width + margin.right);

	focus1.append("circle")
	  .attr("r", 7.5);

	focus1.append("text")
	  .attr("x", 15)
		.attr("dy", ".31em");

	svg1.append("rect")
	  .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
	  .attr("class", "overlay")
	  .attr("width", width)
	  .attr("height", height)
		  .on("mouseover", function() {
		  	focus1.style("display", null);
		  	focus2.style("display", null);
		  	tooltip1.style("visibility", "visible");
		  	tooltip2.style("visibility", "visible");
		  })
		  .on("mouseout", function() {
		  	focus1.style("display", "none");
		  	focus2.style("display", "none");
		  	tooltip1.style("visibility", "visible");
		  	tooltip2.style("visibility", "visible");
		  })
		  .on("mousemove", mousemove);

	// Draw line Norway
	dataNW.forEach(function(d) {
	d.Year = parseTime(d.Year);
	d.Marketvalue = +d.Marketvalue;
	});

	x.domain(d3.extent(dataNW, function(d) { return d.Year; }));
	y.domain([0,1000]);

	g2.append("g")
	  .attr("class", "axis axis--x")
	  .attr("transform", "translate(0," + height + ")")

	g2.append("g")
	  .attr("class", "axis axis--y")

	.append("text")
	  .attr("class", "axis-title")
	  .attr("transform", "rotate(-90)")
	  .attr("y", 6)
	  .attr("dy", ".71em")
	  .style("text-anchor", "end")
	  .attr("fill", "#5D6971")

	g2.append("path")
	  .datum(dataNW)
	  .attr("class", "line")
	  .attr("d", line);

	var focus2 = g2.append("g")
	  .attr("class", "focus")
	  .style("display", "none");

	focus2.append("line")
	  .attr("class", "x-hover-line hover-line")
	  .attr("y1", 0)
	  .attr("y2", height);

	focus2.append("line")
	  .attr("class", "y-hover-line hover-line")
	  .attr("x1", width+margin.right)
	  .attr("x2", width+margin.right);

	focus2.append("circle")
	  .attr("r", 7.5);

	focus2.append("text")
	  .attr("x", 30)
	  .attr("dy", ".31em");

	svg2.append("rect")
	  .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
	  .attr("class", "overlay")
	  .attr("width", width)
	  .attr("height", height)
		  .on("mouseover", function() {
		  	focus2.style("display", null);
		  	focus1.style("display", null);
		  	tooltip2.style("visibility", "visible");
		  	tooltip1.style("visibility", "visible");
		  })
		  .on("mouseout", function() {
		  	focus2.style("display", "none");
		  	focus1.style("display", null);
		  	tooltip2.style("visibility", "visible");
		  	tooltip1.style("visibility", "visible");
		  })
		  .on("mousemove", mousemove);

  	var tooltip1 = d3.select("div.chartstart")

	  .style("position", "relative")
	  .style("text-anchor", "left")
	  .style("z-index", "100")
	  .style("visibility", "hidden");

	tooltip1.append("text1")
	  .attr("x", 10)
	  .attr("dy", ".31em");

	var tooltip2 = d3.select("div.chartstart2")

	  .style("position", "relative")
	  .style("text-anchor", "left")
	  .style("z-index", "100")
	  .style("visibility", "hidden");

	tooltip2.append("text2")
	  .attr("x", 10)
	  .attr("dy", ".31em");

	// Create function for mousemove
	function mousemove() {
	var x0 = x.invert(d3.mouse(this)[0]),
	    i = bisectDate(dataNL, x0, 1),
	    d0 = dataNL[i - 1],
	    d1 = dataNL[i],
	    d = x0 - d0.Year > d1.Year - x0 ? d1 : d0;
		d2 = dataNW[d.Year.getYear() - 69];
	focus1.attr("transform", "translate(" + x(d.Year) + "," + y(d.Marketvalue) + ")");
	focus1.select("text").text(function() { return d.Marketvalue; });
	focus1.select(".x-hover-line").attr("y2", height - y(d.Marketvalue));
	focus1.select(".y-hover-line").attr("x2", width + width);
	focus2.attr("transform", "translate(" + x(d2.Year) + "," + y(d2.Marketvalue) + ")");
	focus2.select("text").text(function() { return d2.Marketvalue; });
	focus2.select(".x-hover-line").attr("y2", height - y(d2.Marketvalue));
	focus2.select(".y-hover-line").attr("x2", width + width);
	//tooltip1.attr("transform", "translate(" + x(d.Year) + "," + y(d.Marketvalue) + ")");
	tooltip1.select("text1").text(function() { return "€ " + d.Marketvalue + ".000.000";  });
	//tooltip2.attr("transform", "translate(" + x(d2.Year) + "," + y(d2.Marketvalue) + ")");
	tooltip2.select("text2").text(function() { return "€ " + d2.Marketvalue + ".000.000"; });

	}
}

<|endoftext|>


<commit_before>import assign from 'fast.js/object/assign';
import udc from 'udc';

function validateValue(...args) {
  const [
    workingValue, blurred, autoValidate, validate, inputElement, validator,
    ] = args;

  let isInvalid = false;

  const handleValidation = (workingValue !== `` || blurred) &&
    autoValidate || validate && inputElement;

  if (handleValidation && validator) {
    isInvalid = !validator(workingValue);
  } else if (handleValidation) {
    isInvalid = !inputElement.checkValidity();
  }

  return isInvalid;
}

function styleLabel(props, labelLeft) {
  let {label} = props;

  if (props.prefix) {
    label = udc(props.label);

    const labelAttributes = label.properties.attributes =
      label.properties.attributes || {};

    labelAttributes.style =
      `${labelAttributes.style || ``};${labelLeft}`.replace(/^;/, ``);
  }

  return label;
}

function model({props$, actions, layout, dialogueName}) {
  return props$.combineLatest(
    actions.focused$,
    actions.blurred$,
    actions.value$,
    layout.inputElement$,
    layout.floatLabelOffsetLeft$,
    (props, ...items) => {
      const [focused, blurred, value, inputElement, floatLabelOffsetLeft] =
        items;

      const {
        autoValidate,
        bindValue,
        disableLabelFloat,
        persistLabelFloat,
        validate,
        validator} = props;

      let workingValue = bindValue || value;

      const isInvalid = validateValue(
        workingValue, blurred, autoValidate, validate, inputElement, validator
      );

      // type="number" hack needed because value is empty until it’s valid.
      // See issue #25.
      const inputHasContent = workingValue || workingValue === 0 ||
        inputElement && inputElement.type === `number` &&
        !inputElement.checkValidity();

      const floatLabel =
        !disableLabelFloat && inputHasContent || persistLabelFloat;

      const labelLeft = floatLabel ? floatLabelOffsetLeft : `left: 0;`;

      const hideLabel = disableLabelFloat && inputHasContent;

      const label = styleLabel(props, labelLeft);

      return assign({},
        props,
        {
          dialogueName,
          label,
          focused,
          value,
          isInvalid,
          floatLabel,
          labelLeft,
          hideLabel,
        });
    }
  );
}

export default model;
<commit_msg>refactor(inputContainer): clean up model<commit_after>import assign from 'fast.js/object/assign';
import udc from 'udc';

function validateValue(value, inputElement, validator) {
  return validator ?
    !validator(value) :
    inputElement && !inputElement.checkValidity();
}

function handleValidation(
  {value, lostFocus, autoValidate, validate, inputElement, validator}
) {
  const shouldValidate = (value !== `` || lostFocus) &&
    autoValidate || validate;

  return shouldValidate ?
    validateValue(value, inputElement, validator) :
    false;
}

function styleLabel(props, labelLeft) {
  let {label} = props;

  if (props.prefix) {
    label = udc(props.label);

    const labelAttributes = label.properties.attributes =
      label.properties.attributes || {};

    labelAttributes.style =
      `${labelAttributes.style || ``};${labelLeft}`.replace(/^;/, ``);
  }

  return label;
}

function model({props$, actions, layout, dialogueName}) {
  return props$.combineLatest(
    actions.focused$,
    actions.blurred$,
    actions.value$,
    layout.inputElement$,
    layout.floatLabelOffsetLeft$,
    (props, ...items) => {
      const [focused, lostFocus, value, inputElement, floatLabelOffsetLeft] =
        items;

      const {
        autoValidate,
        bindValue,
        disableLabelFloat,
        persistLabelFloat,
        validate,
        validator} = props;

      const workingValue = bindValue || value;

      const isInvalid = handleValidation({
        value: workingValue,
        lostFocus,
        autoValidate,
        validate,
        inputElement,
        validator,
      });

      // type="number" hack needed because value is empty until it’s valid.
      // See issue #25.
      const inputHasContent = workingValue || workingValue === 0 ||
        inputElement && inputElement.type === `number` &&
        !inputElement.checkValidity();

      const floatLabel =
        !disableLabelFloat && inputHasContent || persistLabelFloat;

      const labelLeft = floatLabel ? floatLabelOffsetLeft : `left: 0;`;

      const hideLabel = disableLabelFloat && inputHasContent;

      const label = styleLabel(props, labelLeft);

      return assign({},
        props,
        {
          dialogueName,
          label,
          focused,
          value: workingValue,
          isInvalid,
          floatLabel,
          labelLeft,
          hideLabel,
        });
    }
  );
}

export default model;
<|endoftext|>