<commit_before>import lonlat from '@conveyal/lonlat'
import React, { Component, PropTypes } from 'react'
import ReactDOM from 'react-dom'
import { FormGroup, FormControl, InputGroup, DropdownButton, MenuItem } from 'react-bootstrap'
import { connect } from 'react-redux'
import { autocomplete, search } from 'isomorphic-mapzen-search'
import { throttle } from 'throttle-debounce'

import LocationIcon from '../icons/location-icon'
import { setLocation, setLocationToCurrent, clearLocation } from '../../actions/map'
import { addLocationSearch, getCurrentPosition } from '../../actions/location'
import { findNearbyStops } from '../../actions/api'
import { distanceStringImperial } from '../../util/distance'

class LocationField extends Component {
  static propTypes = {
    config: PropTypes.object,
    currentPosition: PropTypes.object,
    hideExistingValue: PropTypes.bool,
    location: PropTypes.object,
    label: PropTypes.string,
    nearbyStops: PropTypes.array,
    sessionSearches: PropTypes.array,
    showClearButton: PropTypes.bool,
    static: PropTypes.bool, // show autocomplete options as fixed/inline element rather than dropdown
    stopsIndex: PropTypes.object,
    type: PropTypes.string, // replace with locationType?

    // callbacks
    onClick: PropTypes.func,
    onLocationSelected: PropTypes.func,

    // dispatch
    addLocationSearch: PropTypes.func,
    clearLocation: PropTypes.func,
    setLocation: PropTypes.func,
    setLocationToCurrent: PropTypes.func
  }

  static defaultProps = {
    showClearButton: true
  }

  constructor (props) {
    super(props)
    this.state = {
      value: props.location !== null && !props.hideExistingValue
        ? props.location.name
        : '',
      menuVisible: false,
      geocodedFeatures: [],
      activeIndex: null
    }
  }

  componentWillReceiveProps (nextProps) {
    if (this.props.location !== nextProps.location) {
      this.setState({
        value: nextProps.location !== null ? nextProps.location.name : '',
        geocodedFeatures: []
      })
    }
  }

  componentDidMount () {
    if (this.props.static) {
      ReactDOM.findDOMNode(this.formControl).focus()
    }
  }

  _geocodeAutocomplete = throttle(1000, (text) => {
    const {MAPZEN_KEY, baseUrl, boundary, focusPoint} = this.props.config.geocoder
    autocomplete({
      apiKey: MAPZEN_KEY,
      boundary,
      focusPoint,
      sources: null,
      text,
      url: baseUrl ? `${baseUrl}/autocomplete` : null
    }).then((result) => {
      this.setState({ geocodedFeatures: result.features })
    }).catch((err) => {
      console.error(err)
    })
  })

  _geocodeSearch (text) {
    const { MAPZEN_KEY, baseUrl, boundary, focusPoint } = this.props.config.geocoder
    search({
      apiKey: MAPZEN_KEY,
      boundary,
      focusPoint,
      text,
      sources: null,
      url: baseUrl ? `${baseUrl}/search` : null,
      format: false // keep as returned GeoJSON
    }).then((result) => {
      console.log(`search results (query=${text})`, result)
      if (result.features.length > 0) {
        // Only replace geocode items if results were found
        this.setState({ geocodedFeatures: result.features })
      } else {
        console.warn('No results found for geocode search. Not replacing results.')
      }
    }).catch((err) => {
      console.error(err)
    })
  }

  _getFormControlClassname () {
    return this.props.type + '-form-control'
  }

  _onClearButtonClick = () => {
    const { type } = this.props
    this.props.clearLocation({ type })
    this.setState({
      value: '',
      geocodedFeatures: []
    })
    ReactDOM.findDOMNode(this.formControl).focus()
  }

  _onDropdownToggle = (v, e) => {
    // if clicked on input form control, keep dropdown open; otherwise, toggle
    const targetIsInput =
      e.target.className.indexOf(this._getFormControlClassname()) !== -1
    const menuVisible = targetIsInput ? true : !this.state.menuVisible
    this.setState({ menuVisible })
  }

  _onTextInputChange = (evt) => {
    this.setState({ value: evt.target.value })
    this._geocodeAutocomplete(evt.target.value)
  }

  _onTextInputClick = () => {
    const { config, currentPosition, onClick } = this.props
    if (typeof onClick === 'function') onClick()
    this.setState({ menuVisible: true })
    if (currentPosition && currentPosition.coords) {
      this.props.findNearbyStops({
        lat: currentPosition.coords.latitude,
        lon: currentPosition.coords.longitude,
        max: config.geocoder.maxNearbyStops || 4
      })
    }
  }

  _onKeyDown = (evt) => {
    const { activeIndex } = this.state

    switch (evt.key) {
      // 'Down' arrow key pressed: move selected menu item down by one position
      case 'ArrowDown':
        if (activeIndex === this.menuItemCount - 1) {
          this.setState({ activeIndex: null })
          break
        }
        this.setState({
          activeIndex: activeIndex === null
            ? 0
            : activeIndex + 1
        })
        break

      // 'Up' arrow key pressed: move selection up by one position
      case 'ArrowUp':
        if (activeIndex === 0) {
          this.setState({ activeIndex: null })
          break
        }
        this.setState({
          activeIndex: activeIndex === null
            ? this.menuItemCount - 1
            : activeIndex - 1
        })
        break

      // 'Enter' keypress serves two purposes:
      //  - If pressed when typing in search string, switch from 'autocomplete'
      //    to 'search' geocosing
      //  - If pressed when dropdown results menu is active, apply the location
      //    associated with current selected menu item
      case 'Enter':
        if (activeIndex) { // Menu is active
          // Retrieve location selection handler from lookup object and invoke
          const locationSelected = this.locationSelectedLookup[activeIndex]
          if (locationSelected) locationSelected()

          // Clear selection & hide the menu
          this.setState({
            menuVisible: false,
            activeIndex: null
          })
        } else { // Menu not active; get geocode 'search' results
          this._geocodeSearch(evt.target.value)
        }

        // Suppress default 'Enter' behavior which causes page to reload
        evt.preventDefault()
        break

      // Any other key pressed: clear active selection
      default:
        this.setState({ activeIndex: null })
    }
  }

  _setLocation (location) {
    if (typeof this.props.onLocationSelected === 'function') {
      this.props.onLocationSelected(location)
    }
    this.props.setLocation({
      type: this.props.type,
      location
    })
  }

  _useCurrentLocation = () => {
    const {currentPosition, getCurrentPosition, setLocationToCurrent, type} = this.props
    if (currentPosition.coords) {
      // We already have geolocation coordinates
      setLocationToCurrent({ type })
    } else {
      // Call geolocation.getCurrentPosition and set as from/to type
      this.setState({fetchingLocation: true})
      getCurrentPosition(type)
    }
  }

  /**
   * Provide alert to user with reason for geolocation error
   */
  _geolocationAlert = () => {
    window.alert(
      `Geolocation either has been disabled for ${window.location.host} or is not available in your browser.\n\nReason: ${this.props.currentPosition.error.message || 'Unknown reason'}`
    )
  }

  render () {
    const {
      currentPosition,
      label,
      showClearButton,
      static: isStatic,
      suppressNearby,
      type,
      nearbyStops
    } = this.props

    const { activeIndex } = this.state

    let geocodedFeatures = this.state.geocodedFeatures
    if (geocodedFeatures.length > 5) geocodedFeatures = geocodedFeatures.slice(0, 5)

    let sessionSearches = this.props.sessionSearches
    if (sessionSearches.length > 5) sessionSearches = sessionSearches.slice(0, 5)

    // Assemble menu contents, to be displayed either as dropdown or static panel.
    // Menu items are created in four phases: (1) the current location, (2) any
    // geocoder search results; (3) nearby transit stops; and (4) saved searches

    let menuItems = [] // array of menu items for display (may include non-selectable items e.g. dividers/headings)
    let itemIndex = 0 // the index of the current location-associated menu item (excluding non-selectable items)
    this.locationSelectedLookup = {} // maps itemIndex to a location selection handler (for use by the _onKeyDown method)

    /* 1) Process the current location */
    let locationSelected, optionIcon, optionTitle

    if (!currentPosition.error) { // current position detected successfully
      locationSelected = this._useCurrentLocation
      optionIcon = 'location-arrow'
      optionTitle = 'Use Current Location'
    } else { // error detecting current position
      locationSelected = this._geolocationAlert
      optionIcon = 'ban'
      optionTitle = 'Current location not available'
    }

    // Add to the selection handler lookup (for use in _onKeyDown)
    this.locationSelectedLookup[itemIndex] = locationSelected

    if (!suppressNearby) {
      // Create and add the option item to the menu items array
      const currentLocationOption = createOption(optionIcon, optionTitle, locationSelected, itemIndex === activeIndex)
      menuItems.push(currentLocationOption)
      itemIndex++
    }

    /* 2) Process geocode search result option(s) */
    if (geocodedFeatures.length > 0) {
      // Add the menu sub-heading (not a selectable item)
      menuItems.push(<MenuItem header key='sr-header'>Search Results</MenuItem>)

      // Iterate through the geocoder results
      menuItems = menuItems.concat(geocodedFeatures.map(feature => {
        // Create the selection handler
        const locationSelected = () => {
          // Construct the location
          const location = lonlat.fromCoordinates(feature.geometry.coordinates)
          location.name = feature.properties.label
          // Set the current location
          this._setLocation(location)
          // Add to the location search history
          this.props.addLocationSearch({ location })
        }

        // Add to the selection handler lookup (for use in _onKeyDown)
        this.locationSelectedLookup[itemIndex] = locationSelected

        // Create and return the option menu item
        const option = createOption('map-pin', feature.properties.label, locationSelected, itemIndex === activeIndex)
        itemIndex++
        return option
      }))
    }

    /* 3) Process nearby transit stop options */
    if (nearbyStops.length > 0 && !suppressNearby) {
      // Add the menu sub-heading (not a selectable item)
      menuItems.push(<MenuItem header key='ns-header'>Nearby Stops</MenuItem>)

      // Iterate through the found nearby stops
      menuItems = menuItems.concat(nearbyStops.map(stopId => {
        // Constuct the location
        const stop = this.props.stopsIndex[stopId]
        const location = {
          name: stop.name,
          lat: stop.lat,
          lon: stop.lon
        }

        // Create the location selected handler
        const locationSelected = () => { this._setLocation(location) }

        // Add to the selection handler lookup (for use in _onKeyDown)
        this.locationSelectedLookup[itemIndex] = locationSelected

        // Create and return the option menu item
        const option = createTransitStopOption(stop, locationSelected, itemIndex === activeIndex)
        itemIndex++
        return option
      }))
    }

    /* 4) Process recent search history options */
    if (sessionSearches.length > 0) {
      // Add the menu sub-heading (not a selectable item)
      menuItems.push(<MenuItem header key='ss-header'>Recently Searched</MenuItem>)

      // Iterate through any saved locations
      menuItems = menuItems.concat(sessionSearches.map(location => {
        // Create the location-selected handler
        const locationSelected = () => { this._setLocation(location) }

        // Add to the selection handler lookup (for use in _onKeyDown)
        this.locationSelectedLookup[itemIndex] = locationSelected

        // Create and return the option menu item
        const option = createOption('search', location.name, locationSelected, itemIndex === activeIndex)
        itemIndex++
        return option
      }))
    }

    // Store the number of location-associated items for reference in the _onKeyDown method
    this.menuItemCount = itemIndex

    /** the text input element **/
    const placeholder = currentPosition.fetching === type
      ? 'Fetching location...'
      : label || type
    const textControl = <FormControl
      ref={ctl => { this.formControl = ctl }}
      className={this._getFormControlClassname()}
      type='text'
      value={this.state.value}
      placeholder={placeholder}
      onChange={this._onTextInputChange}
      onClick={this._onTextInputClick}
      onKeyDown={this._onKeyDown}
    />

    /** the clear ('X') button add-on */
    const clearButton = <InputGroup.Addon onClick={this._onClearButtonClick}>

    </InputGroup.Addon>

    if (isStatic) {
      // 'static' mode (menu is displayed alongside input, e.g., for mobile view)
      return (

          <form>
            <FormGroup>
              <InputGroup>
                <InputGroup.Addon>
                  <LocationIcon type={type} />
                </InputGroup.Addon>
                {textControl}
                {showClearButton && clearButton}
              </InputGroup>
            </FormGroup>
          </form>

            {menuItems.length > 0 // Show typing prompt to avoid empty screen
              ? menuItems
              : <MenuItem header className={'text-center'}>
                Begin typing to search for locations
              </MenuItem>
            }

      )
    } else {
      // default display mode with dropdown menu
      return (
        <form>
          <FormGroup className='location-field'>
            <InputGroup>
              {/* location field icon -- also serves as dropdown anchor */}
              <DropdownButton
                componentClass={InputGroup.Button}
                open={this.state.menuVisible}
                onToggle={this._onDropdownToggle}
                id='location-dropdown'
                title={<LocationIcon type={type} />}
                noCaret
              >
                {menuItems}
              </DropdownButton>
              {textControl}
              {showClearButton && clearButton}
            </InputGroup>
          </FormGroup>
        </form>
      )
    }
  }
}

// helper functions for dropdown options

let itemKey = 0

function createOption (icon, title, onSelect, isActive) {
  return <MenuItem className='location-option' onSelect={onSelect} key={itemKey++} active={isActive}>

<i className={`fa fa-${icon}`} />

{title}

  </MenuItem>
}

function createTransitStopOption (stop, onSelect, isActive) {
  return <MenuItem className='location-option' onSelect={onSelect} key={itemKey++} active={isActive}>

{distanceStringImperial(stop.dist, true)}

{stop.name} ({stop.code})

          {(stop.routes || []).map((route, i) => {
            const name = route.shortName || route.longName
            return (
              <span key={`route-${i}`} className='route'>
                {name}

            )
          })}

  </MenuItem>
}

// connect to redux store

const mapStateToProps = (state, ownProps) => {
  return {
    config: state.otp.config,
    location: state.otp.currentQuery[ownProps.type],
    currentPosition: state.otp.location.currentPosition,
    sessionSearches: state.otp.location.sessionSearches,
    nearbyStops: state.otp.location.nearbyStops,
    stopsIndex: state.otp.transitIndex.stops
  }
}

const mapDispatchToProps = {
  addLocationSearch,
  findNearbyStops,
  getCurrentPosition,
  setLocation,
  setLocationToCurrent,
  clearLocation
}

export default connect(mapStateToProps, mapDispatchToProps)(LocationField)
<commit_msg>fix(location): clarify index null check<commit_after>import lonlat from '@conveyal/lonlat'
import React, { Component, PropTypes } from 'react'
import ReactDOM from 'react-dom'
import { FormGroup, FormControl, InputGroup, DropdownButton, MenuItem } from 'react-bootstrap'
import { connect } from 'react-redux'
import { autocomplete, search } from 'isomorphic-mapzen-search'
import { throttle } from 'throttle-debounce'

import LocationIcon from '../icons/location-icon'
import { setLocation, setLocationToCurrent, clearLocation } from '../../actions/map'
import { addLocationSearch, getCurrentPosition } from '../../actions/location'
import { findNearbyStops } from '../../actions/api'
import { distanceStringImperial } from '../../util/distance'

class LocationField extends Component {
  static propTypes = {
    config: PropTypes.object,
    currentPosition: PropTypes.object,
    hideExistingValue: PropTypes.bool,
    location: PropTypes.object,
    label: PropTypes.string,
    nearbyStops: PropTypes.array,
    sessionSearches: PropTypes.array,
    showClearButton: PropTypes.bool,
    static: PropTypes.bool, // show autocomplete options as fixed/inline element rather than dropdown
    stopsIndex: PropTypes.object,
    type: PropTypes.string, // replace with locationType?

    // callbacks
    onClick: PropTypes.func,
    onLocationSelected: PropTypes.func,

    // dispatch
    addLocationSearch: PropTypes.func,
    clearLocation: PropTypes.func,
    setLocation: PropTypes.func,
    setLocationToCurrent: PropTypes.func
  }

  static defaultProps = {
    showClearButton: true
  }

  constructor (props) {
    super(props)
    this.state = {
      value: props.location !== null && !props.hideExistingValue
        ? props.location.name
        : '',
      menuVisible: false,
      geocodedFeatures: [],
      activeIndex: null
    }
  }

  componentWillReceiveProps (nextProps) {
    if (this.props.location !== nextProps.location) {
      this.setState({
        value: nextProps.location !== null ? nextProps.location.name : '',
        geocodedFeatures: []
      })
    }
  }

  componentDidMount () {
    if (this.props.static) {
      ReactDOM.findDOMNode(this.formControl).focus()
    }
  }

  _geocodeAutocomplete = throttle(1000, (text) => {
    const {MAPZEN_KEY, baseUrl, boundary, focusPoint} = this.props.config.geocoder
    autocomplete({
      apiKey: MAPZEN_KEY,
      boundary,
      focusPoint,
      sources: null,
      text,
      url: baseUrl ? `${baseUrl}/autocomplete` : null
    }).then((result) => {
      this.setState({ geocodedFeatures: result.features })
    }).catch((err) => {
      console.error(err)
    })
  })

  _geocodeSearch (text) {
    const { MAPZEN_KEY, baseUrl, boundary, focusPoint } = this.props.config.geocoder
    search({
      apiKey: MAPZEN_KEY,
      boundary,
      focusPoint,
      text,
      sources: null,
      url: baseUrl ? `${baseUrl}/search` : null,
      format: false // keep as returned GeoJSON
    }).then((result) => {
      console.log(`search results (query=${text})`, result)
      if (result.features.length > 0) {
        // Only replace geocode items if results were found
        this.setState({ geocodedFeatures: result.features })
      } else {
        console.warn('No results found for geocode search. Not replacing results.')
      }
    }).catch((err) => {
      console.error(err)
    })
  }

  _getFormControlClassname () {
    return this.props.type + '-form-control'
  }

  _onClearButtonClick = () => {
    const { type } = this.props
    this.props.clearLocation({ type })
    this.setState({
      value: '',
      geocodedFeatures: []
    })
    ReactDOM.findDOMNode(this.formControl).focus()
  }

  _onDropdownToggle = (v, e) => {
    // if clicked on input form control, keep dropdown open; otherwise, toggle
    const targetIsInput =
      e.target.className.indexOf(this._getFormControlClassname()) !== -1
    const menuVisible = targetIsInput ? true : !this.state.menuVisible
    this.setState({ menuVisible })
  }

  _onTextInputChange = (evt) => {
    this.setState({ value: evt.target.value })
    this._geocodeAutocomplete(evt.target.value)
  }

  _onTextInputClick = () => {
    const { config, currentPosition, onClick } = this.props
    if (typeof onClick === 'function') onClick()
    this.setState({ menuVisible: true })
    if (currentPosition && currentPosition.coords) {
      this.props.findNearbyStops({
        lat: currentPosition.coords.latitude,
        lon: currentPosition.coords.longitude,
        max: config.geocoder.maxNearbyStops || 4
      })
    }
  }

  _onKeyDown = (evt) => {
    const { activeIndex } = this.state

    switch (evt.key) {
      // 'Down' arrow key pressed: move selected menu item down by one position
      case 'ArrowDown':
        if (activeIndex === this.menuItemCount - 1) {
          this.setState({ activeIndex: null })
          break
        }
        this.setState({
          activeIndex: activeIndex === null
            ? 0
            : activeIndex + 1
        })
        break

      // 'Up' arrow key pressed: move selection up by one position
      case 'ArrowUp':
        if (activeIndex === 0) {
          this.setState({ activeIndex: null })
          break
        }
        this.setState({
          activeIndex: activeIndex === null
            ? this.menuItemCount - 1
            : activeIndex - 1
        })
        break

      // 'Enter' keypress serves two purposes:
      //  - If pressed when typing in search string, switch from 'autocomplete'
      //    to 'search' geocoding
      //  - If pressed when dropdown results menu is active, apply the location
      //    associated with current selected menu item
      case 'Enter':
        if (typeof activeIndex === 'number') { // Menu is active
          // Retrieve location selection handler from lookup object and invoke
          const locationSelected = this.locationSelectedLookup[activeIndex]
          if (locationSelected) locationSelected()

          // Clear selection & hide the menu
          this.setState({
            menuVisible: false,
            activeIndex: null
          })
        } else { // Menu not active; get geocode 'search' results
          this._geocodeSearch(evt.target.value)
        }

        // Suppress default 'Enter' behavior which causes page to reload
        evt.preventDefault()
        break

      // Any other key pressed: clear active selection
      default:
        this.setState({ activeIndex: null })
    }
  }

  _setLocation (location) {
    if (typeof this.props.onLocationSelected === 'function') {
      this.props.onLocationSelected(location)
    }
    this.props.setLocation({
      type: this.props.type,
      location
    })
  }

  _useCurrentLocation = () => {
    const {currentPosition, getCurrentPosition, setLocationToCurrent, type} = this.props
    if (currentPosition.coords) {
      // We already have geolocation coordinates
      setLocationToCurrent({ type })
    } else {
      // Call geolocation.getCurrentPosition and set as from/to type
      this.setState({fetchingLocation: true})
      getCurrentPosition(type)
    }
  }

  /**
   * Provide alert to user with reason for geolocation error
   */
  _geolocationAlert = () => {
    window.alert(
      `Geolocation either has been disabled for ${window.location.host} or is not available in your browser.\n\nReason: ${this.props.currentPosition.error.message || 'Unknown reason'}`
    )
  }

  render () {
    const {
      currentPosition,
      label,
      showClearButton,
      static: isStatic,
      suppressNearby,
      type,
      nearbyStops
    } = this.props

    const { activeIndex } = this.state

    let geocodedFeatures = this.state.geocodedFeatures
    if (geocodedFeatures.length > 5) geocodedFeatures = geocodedFeatures.slice(0, 5)

    let sessionSearches = this.props.sessionSearches
    if (sessionSearches.length > 5) sessionSearches = sessionSearches.slice(0, 5)

    // Assemble menu contents, to be displayed either as dropdown or static panel.
    // Menu items are created in four phases: (1) the current location, (2) any
    // geocoder search results; (3) nearby transit stops; and (4) saved searches

    let menuItems = [] // array of menu items for display (may include non-selectable items e.g. dividers/headings)
    let itemIndex = 0 // the index of the current location-associated menu item (excluding non-selectable items)
    this.locationSelectedLookup = {} // maps itemIndex to a location selection handler (for use by the _onKeyDown method)

    /* 1) Process the current location */
    let locationSelected, optionIcon, optionTitle

    if (!currentPosition.error) { // current position detected successfully
      locationSelected = this._useCurrentLocation
      optionIcon = 'location-arrow'
      optionTitle = 'Use Current Location'
    } else { // error detecting current position
      locationSelected = this._geolocationAlert
      optionIcon = 'ban'
      optionTitle = 'Current location not available'
    }

    // Add to the selection handler lookup (for use in _onKeyDown)
    this.locationSelectedLookup[itemIndex] = locationSelected

    if (!suppressNearby) {
      // Create and add the option item to the menu items array
      const currentLocationOption = createOption(optionIcon, optionTitle, locationSelected, itemIndex === activeIndex)
      menuItems.push(currentLocationOption)
      itemIndex++
    }

    /* 2) Process geocode search result option(s) */
    if (geocodedFeatures.length > 0) {
      // Add the menu sub-heading (not a selectable item)
      menuItems.push(<MenuItem header key='sr-header'>Search Results</MenuItem>)

      // Iterate through the geocoder results
      menuItems = menuItems.concat(geocodedFeatures.map(feature => {
        // Create the selection handler
        const locationSelected = () => {
          // Construct the location
          const location = lonlat.fromCoordinates(feature.geometry.coordinates)
          location.name = feature.properties.label
          // Set the current location
          this._setLocation(location)
          // Add to the location search history
          this.props.addLocationSearch({ location })
        }

        // Add to the selection handler lookup (for use in _onKeyDown)
        this.locationSelectedLookup[itemIndex] = locationSelected

        // Create and return the option menu item
        const option = createOption('map-pin', feature.properties.label, locationSelected, itemIndex === activeIndex)
        itemIndex++
        return option
      }))
    }

    /* 3) Process nearby transit stop options */
    if (nearbyStops.length > 0 && !suppressNearby) {
      // Add the menu sub-heading (not a selectable item)
      menuItems.push(<MenuItem header key='ns-header'>Nearby Stops</MenuItem>)

      // Iterate through the found nearby stops
      menuItems = menuItems.concat(nearbyStops.map(stopId => {
        // Constuct the location
        const stop = this.props.stopsIndex[stopId]
        const location = {
          name: stop.name,
          lat: stop.lat,
          lon: stop.lon
        }

        // Create the location selected handler
        const locationSelected = () => { this._setLocation(location) }

        // Add to the selection handler lookup (for use in _onKeyDown)
        this.locationSelectedLookup[itemIndex] = locationSelected

        // Create and return the option menu item
        const option = createTransitStopOption(stop, locationSelected, itemIndex === activeIndex)
        itemIndex++
        return option
      }))
    }

    /* 4) Process recent search history options */
    if (sessionSearches.length > 0) {
      // Add the menu sub-heading (not a selectable item)
      menuItems.push(<MenuItem header key='ss-header'>Recently Searched</MenuItem>)

      // Iterate through any saved locations
      menuItems = menuItems.concat(sessionSearches.map(location => {
        // Create the location-selected handler
        const locationSelected = () => { this._setLocation(location) }

        // Add to the selection handler lookup (for use in _onKeyDown)
        this.locationSelectedLookup[itemIndex] = locationSelected

        // Create and return the option menu item
        const option = createOption('search', location.name, locationSelected, itemIndex === activeIndex)
        itemIndex++
        return option
      }))
    }

    // Store the number of location-associated items for reference in the _onKeyDown method
    this.menuItemCount = itemIndex

    /** the text input element **/
    const placeholder = currentPosition.fetching === type
      ? 'Fetching location...'
      : label || type
    const textControl = <FormControl
      ref={ctl => { this.formControl = ctl }}
      className={this._getFormControlClassname()}
      type='text'
      value={this.state.value}
      placeholder={placeholder}
      onChange={this._onTextInputChange}
      onClick={this._onTextInputClick}
      onKeyDown={this._onKeyDown}
    />

    /** the clear ('X') button add-on */
    const clearButton = <InputGroup.Addon onClick={this._onClearButtonClick}>

    </InputGroup.Addon>

    if (isStatic) {
      // 'static' mode (menu is displayed alongside input, e.g., for mobile view)
      return (

          <form>
            <FormGroup>
              <InputGroup>
                <InputGroup.Addon>
                  <LocationIcon type={type} />
                </InputGroup.Addon>
                {textControl}
                {showClearButton && clearButton}
              </InputGroup>
            </FormGroup>
          </form>

            {menuItems.length > 0 // Show typing prompt to avoid empty screen
              ? menuItems
              : <MenuItem header className={'text-center'}>
                Begin typing to search for locations
              </MenuItem>
            }

      )
    } else {
      // default display mode with dropdown menu
      return (
        <form>
          <FormGroup className='location-field'>
            <InputGroup>
              {/* location field icon -- also serves as dropdown anchor */}
              <DropdownButton
                componentClass={InputGroup.Button}
                open={this.state.menuVisible}
                onToggle={this._onDropdownToggle}
                id='location-dropdown'
                title={<LocationIcon type={type} />}
                noCaret
              >
                {menuItems}
              </DropdownButton>
              {textControl}
              {showClearButton && clearButton}
            </InputGroup>
          </FormGroup>
        </form>
      )
    }
  }
}

// helper functions for dropdown options

let itemKey = 0

function createOption (icon, title, onSelect, isActive) {
  return <MenuItem className='location-option' onSelect={onSelect} key={itemKey++} active={isActive}>

<i className={`fa fa-${icon}`} />

{title}

  </MenuItem>
}

function createTransitStopOption (stop, onSelect, isActive) {
  return <MenuItem className='location-option' onSelect={onSelect} key={itemKey++} active={isActive}>

{distanceStringImperial(stop.dist, true)}

{stop.name} ({stop.code})

          {(stop.routes || []).map((route, i) => {
            const name = route.shortName || route.longName
            return (
              <span key={`route-${i}`} className='route'>
                {name}

            )
          })}

  </MenuItem>
}

// connect to redux store

const mapStateToProps = (state, ownProps) => {
  return {
    config: state.otp.config,
    location: state.otp.currentQuery[ownProps.type],
    currentPosition: state.otp.location.currentPosition,
    sessionSearches: state.otp.location.sessionSearches,
    nearbyStops: state.otp.location.nearbyStops,
    stopsIndex: state.otp.transitIndex.stops
  }
}

const mapDispatchToProps = {
  addLocationSearch,
  findNearbyStops,
  getCurrentPosition,
  setLocation,
  setLocationToCurrent,
  clearLocation
}

export default connect(mapStateToProps, mapDispatchToProps)(LocationField)
<|endoftext|>


<commit_before>'use strict';

var assert = require('assert');
var testHelper = require('../helper');

describe('deprecated-sql-function analysis', function () {
    var QUERY_SOURCE = [
        'WITH sources AS (',
        '   select i as cartodb_id, st_setsrid(st_makepoint(i,i), 4326) as the_geom',
        '   from generate_series(1,3) as i',
        ')',
        'select *, st_x(the_geom) as x, st_y(the_geom) as y from sources'
    ].join('\n');

    describe('basics', function() {
        before(function(done) {
            testHelper.executeQuery([
                'CREATE OR REPLACE FUNCTION DEP_EXT_test_deprecated_fn(',
                '    query text, columns text[], a numeric, b numeric, c numeric, d text, table_name text, operation text',
                ')',
                'RETURNS VOID AS $$',
                '    BEGIN',
                '        IF operation = \'create\' THEN',
                '            EXECUTE \'CREATE TABLE \' || table_name || \' ',
                '                (cartodb_id numeric, the_geom geometry, a int, b int, c int)\';',
                '        ELSIF operation = \'populate\' THEN',
                '            EXECUTE \'INSERT INTO \' || table_name || \' ',
                '                SELECT',
                '                    cartodb_id,',
                '                    st_setsrid(st_makepoint(st_x(the_geom) + \' || a || \',',
                '                    st_y(the_geom) + \' || b + c || \'), 4326) the_geom,',
                '                    \' || a || \', \' || b || \', \' || c || \'',
                '                FROM (\' || query || \') _q\';',
                '        END IF;',
                '    END;',
                '$$ LANGUAGE plpgsql;'
            ].join('\n'), done);
        });

        after(function(done) {
            testHelper.executeQuery(
                'DROP FUNCTION DEP_EXT_test_deprecated_fn(text, text[], numeric, numeric, numeric, text, text, text)',
                done
            );
        });

        function deprecatedSqlFnDefinition(fnArgs) {
            return {
                type: 'deprecated-sql-function',
                params: {
                    function_name: 'DEP_EXT_test_deprecated_fn',
                    primary_source: {
                        type: 'source',
                        params: {
                            query: QUERY_SOURCE
                        }
                    },
                    function_args: fnArgs || [1, 2, 3, 'wadus'],
                }
            };
        }

        function expectedRows(a, b, c) {
            return [
                {
                    cartodb_id: 1,
                    a: a,
                    b: b,
                    c: c,
                    st_x: 1 + a,
                    st_y: 1 + b + c
                },
                {
                    cartodb_id: 2,
                    a: a,
                    b: b,
                    c: c,
                    st_x: 2 + a,
                    st_y: 2 + b + c
                },
                {
                    cartodb_id: 3,
                    a: a,
                    b: b,
                    c: c,
                    st_x: 3 + a,
                    st_y: 3 + b + c
                },
            ];
        }

        it('happy case', function (done) {
            testHelper.createAnalyses(deprecatedSqlFnDefinition(), function(err, result) {
                assert.ifError(err);
                var query = 'select cartodb_id, a, b, c, st_x(the_geom), st_y(the_geom) from (' +
                    result.getRoot().getQuery() +
                    ') _q';
                testHelper.getRows(query, function(err, rows) {
                    assert.ifError(err);
                    assert.equal(rows.length, 3);
                    assert.deepEqual(rows, expectedRows(1, 2, 3));
                    return done();
                });
            });
        });

        it('change extra args', function (done) {
            var extraArgs = [9, 8, 7, 'wadus'];
            testHelper.createAnalyses(deprecatedSqlFnDefinition(extraArgs), function(err, result) {
                assert.ifError(err);
                var query = 'select cartodb_id, a, b, c, st_x(the_geom), st_y(the_geom) from (' +
                    result.getRoot().getQuery() +
                    ') _q';
                testHelper.getRows(query, function(err, rows) {
                    assert.ifError(err);
                    assert.equal(rows.length, 3);
                    assert.deepEqual(rows, expectedRows.apply(null, extraArgs));
                    return done();
                });
            });
        });

    });

    describe('invalid schema', function() {

        function createInvalidDeprecatedFunctionQuery(fnName, tableColumns) {
            var tKeys = Object.keys(tableColumns);
            var sep = (tKeys.length > 0) ? ',' : '';
            var columns = tKeys.map(function(c) { return tableColumns[c]; }).join(',') + sep;
            var selectAsColumns = tKeys.map(function(c) { return c; }).join(',') + sep;
            return [
                'CREATE OR REPLACE FUNCTION ' + fnName + '(',
                '    query text, columns text[], buster numeric, table_name text, operation text',
                ')',
                'RETURNS VOID AS $$',
                '    BEGIN',
                '        IF operation = \'create\' THEN',
                '            EXECUTE \'CREATE TABLE \' || table_name || \' (', columns, 'a int, b int, c int)\';',
                '        ELSIF operation = \'populate\' THEN',
                '            EXECUTE \'INSERT INTO \' || table_name || \' ',
                '                SELECT', selectAsColumns, '1 as a, 2 as b, 3 as c',
                '                FROM (\' || query || \') _q\';',
                '        END IF;',
                '    END;',
                '$$ LANGUAGE plpgsql;'
            ].join('\n');
        }

        function invalidSchemaDeprecatedSqlFnDefinition(fnName, buster) {
            return {
                type: 'deprecated-sql-function',
                params: {
                    function_name: fnName,
                    primary_source: {
                        type: 'source',
                        params: {
                            query: QUERY_SOURCE
                        }
                    },
                    function_args: [buster]
                }
            };
        }

        function mandatoryColumns() {
            return {
                cartodb_id: 'cartodb_id numeric',
                the_geom: 'the_geom geometry'
            };
        }

        describe('missing columns', function() {
            var fnName = 'DEP_EXT_test_deprecated_fn_invalid_schema';

            afterEach(function(done) {
                testHelper.executeQuery(
                    'DROP FUNCTION ' + fnName + '(text, text[], numeric, text, text)',
                    done
                );
            });

            var missingColumnScenarios = [
                {
                    columnsToMiss: ['cartodb_id']
                },
                {
                    columnsToMiss: ['the_geom']
                },
                {
                    columnsToMiss: ['cartodb_id', 'the_geom']
                }
            ];

            missingColumnScenarios.forEach(function(scenario, buster) {
                var missingColumns = scenario.columnsToMiss.join(' and ');
                it('should fail on missing ' + missingColumns, function (done) {
                    var tableColumns = mandatoryColumns();
                    scenario.columnsToMiss.forEach(function(columnToMiss) {
                        delete tableColumns[columnToMiss];
                    });
                    var expectedMessage = scenario.columnsToMiss.map(function(columnToMiss) {
                        return 'Missing required column `' + columnToMiss + '`';
                    }).join('; ');
                    testHelper.executeQuery(createInvalidDeprecatedFunctionQuery(fnName, tableColumns), function(err) {
                        assert.ifError(err);
                        var def = invalidSchemaDeprecatedSqlFnDefinition(fnName, buster);
                        testHelper.createAnalyses(def, function(err) {
                            assert.ok(err);
                            assert.equal(err.message, 'Validation failed: ' + expectedMessage + '.');
                            return done();
                        });
                    });
                });
            });
        });

        describe('invalid column types', function() {
            var fnName = 'DEP_EXT_test_deprecated_fn_invalid_type';

            afterEach(function(done) {
                testHelper.executeQuery(
                    'DROP FUNCTION ' + fnName + '(text, text[], numeric, text, text)',
                    done
                );
            });

            var invalidTypeScenarios = [
                {
                    column: 'cartodb_id',
                    expects: 'number',
                    tableColumns: {
                        'cartodb_id::text': 'cartodb_id text',
                        the_geom: 'the_geom geometry'
                    }
                },
                {
                    column: 'the_geom',
                    expects: 'geometry',
                    tableColumns: {
                        cartodb_id: 'cartodb_id numeric',
                        'ST_AsText(the_geom)': 'the_geom text'
                    }
                }
            ];
            invalidTypeScenarios.forEach(function(scenario, index) {
                it('should fail for ' + scenario.column + ' invalid column type', function (done) {
                    var createFnQuery = createInvalidDeprecatedFunctionQuery(fnName, scenario.tableColumns);
                    testHelper.executeQuery(createFnQuery, function(err) {
                        assert.ifError(err);
                        var def = invalidSchemaDeprecatedSqlFnDefinition(fnName, index);
                        testHelper.createAnalyses(def, function(err) {
                            assert.ok(err);
                            assert.equal(
                                err.message,
                                'Validation failed: Invalid type for column "' +
                                    scenario.column +
                                    '": expected `' + scenario.expects +
                                    '` got `string`.'
                            );
                            return done();
                        });
                    });
                });
            });
        });
    });

});
<commit_msg>Test errors are raised properly<commit_after>'use strict';

var assert = require('assert');
var testHelper = require('../helper');

describe('deprecated-sql-function analysis', function () {
    var QUERY_SOURCE = [
        'WITH sources AS (',
        '   select i as cartodb_id, st_setsrid(st_makepoint(i,i), 4326) as the_geom',
        '   from generate_series(1,3) as i',
        ')',
        'select *, st_x(the_geom) as x, st_y(the_geom) as y from sources'
    ].join('\n');

    describe('basics', function() {
        before(function(done) {
            testHelper.executeQuery([
                'CREATE OR REPLACE FUNCTION DEP_EXT_test_deprecated_fn(',
                '    query text, columns text[], a numeric, b numeric, c numeric, d text, table_name text, operation text',
                ')',
                'RETURNS VOID AS $$',
                '    BEGIN',
                '        IF operation = \'create\' THEN',
                '            EXECUTE \'CREATE TABLE \' || table_name || \' ',
                '                (cartodb_id numeric, the_geom geometry, a int, b int, c int)\';',
                '        ELSIF operation = \'populate\' THEN',
                '            EXECUTE \'INSERT INTO \' || table_name || \' ',
                '                SELECT',
                '                    cartodb_id,',
                '                    st_setsrid(st_makepoint(st_x(the_geom) + \' || a || \',',
                '                    st_y(the_geom) + \' || b + c || \'), 4326) the_geom,',
                '                    \' || a || \', \' || b || \', \' || c || \'',
                '                FROM (\' || query || \') _q\';',
                '        END IF;',
                '    END;',
                '$$ LANGUAGE plpgsql;'
            ].join('\n'), done);
        });

        after(function(done) {
            testHelper.executeQuery(
                'DROP FUNCTION DEP_EXT_test_deprecated_fn(text, text[], numeric, numeric, numeric, text, text, text)',
                done
            );
        });

        function deprecatedSqlFnDefinition(fnArgs) {
            return {
                type: 'deprecated-sql-function',
                params: {
                    function_name: 'DEP_EXT_test_deprecated_fn',
                    primary_source: {
                        type: 'source',
                        params: {
                            query: QUERY_SOURCE
                        }
                    },
                    function_args: fnArgs || [1, 2, 3, 'wadus'],
                }
            };
        }

        function expectedRows(a, b, c) {
            return [
                {
                    cartodb_id: 1,
                    a: a,
                    b: b,
                    c: c,
                    st_x: 1 + a,
                    st_y: 1 + b + c
                },
                {
                    cartodb_id: 2,
                    a: a,
                    b: b,
                    c: c,
                    st_x: 2 + a,
                    st_y: 2 + b + c
                },
                {
                    cartodb_id: 3,
                    a: a,
                    b: b,
                    c: c,
                    st_x: 3 + a,
                    st_y: 3 + b + c
                },
            ];
        }

        it('happy case', function (done) {
            testHelper.createAnalyses(deprecatedSqlFnDefinition(), function(err, result) {
                assert.ifError(err);
                var query = 'select cartodb_id, a, b, c, st_x(the_geom), st_y(the_geom) from (' +
                    result.getRoot().getQuery() +
                    ') _q';
                testHelper.getRows(query, function(err, rows) {
                    assert.ifError(err);
                    assert.equal(rows.length, 3);
                    assert.deepEqual(rows, expectedRows(1, 2, 3));
                    return done();
                });
            });
        });

        it('change extra args', function (done) {
            var extraArgs = [9, 8, 7, 'wadus'];
            testHelper.createAnalyses(deprecatedSqlFnDefinition(extraArgs), function(err, result) {
                assert.ifError(err);
                var query = 'select cartodb_id, a, b, c, st_x(the_geom), st_y(the_geom) from (' +
                    result.getRoot().getQuery() +
                    ') _q';
                testHelper.getRows(query, function(err, rows) {
                    assert.ifError(err);
                    assert.equal(rows.length, 3);
                    assert.deepEqual(rows, expectedRows.apply(null, extraArgs));
                    return done();
                });
            });
        });

    });

    describe('invalid schema', function() {

        function createInvalidDeprecatedFunctionQuery(fnName, tableColumns) {
            var tKeys = Object.keys(tableColumns);
            var sep = (tKeys.length > 0) ? ',' : '';
            var columns = tKeys.map(function(c) { return tableColumns[c]; }).join(',') + sep;
            var selectAsColumns = tKeys.map(function(c) { return c; }).join(',') + sep;
            return [
                'CREATE OR REPLACE FUNCTION ' + fnName + '(',
                '    query text, columns text[], buster numeric, table_name text, operation text',
                ')',
                'RETURNS VOID AS $$',
                '    BEGIN',
                '        IF operation = \'create\' THEN',
                '            EXECUTE \'CREATE TABLE \' || table_name || \' (', columns, 'a int, b int, c int)\';',
                '        ELSIF operation = \'populate\' THEN',
                '            EXECUTE \'INSERT INTO \' || table_name || \' ',
                '                SELECT', selectAsColumns, '1 as a, 2 as b, 3 as c',
                '                FROM (\' || query || \') _q\';',
                '        END IF;',
                '    END;',
                '$$ LANGUAGE plpgsql;'
            ].join('\n');
        }

        function invalidSchemaDeprecatedSqlFnDefinition(fnName, buster) {
            return {
                type: 'deprecated-sql-function',
                params: {
                    function_name: fnName,
                    primary_source: {
                        type: 'source',
                        params: {
                            query: QUERY_SOURCE
                        }
                    },
                    function_args: [buster]
                }
            };
        }

        function mandatoryColumns() {
            return {
                cartodb_id: 'cartodb_id numeric',
                the_geom: 'the_geom geometry'
            };
        }

        describe('missing columns', function() {
            var fnName = 'DEP_EXT_test_deprecated_fn_invalid_schema';

            afterEach(function(done) {
                testHelper.executeQuery(
                    'DROP FUNCTION ' + fnName + '(text, text[], numeric, text, text)',
                    done
                );
            });

            var missingColumnScenarios = [
                {
                    columnsToMiss: ['cartodb_id']
                },
                {
                    columnsToMiss: ['the_geom']
                },
                {
                    columnsToMiss: ['cartodb_id', 'the_geom']
                }
            ];

            missingColumnScenarios.forEach(function(scenario, buster) {
                var missingColumns = scenario.columnsToMiss.join(' and ');
                it('should fail on missing ' + missingColumns, function (done) {
                    var tableColumns = mandatoryColumns();
                    scenario.columnsToMiss.forEach(function(columnToMiss) {
                        delete tableColumns[columnToMiss];
                    });
                    var expectedMessage = scenario.columnsToMiss.map(function(columnToMiss) {
                        return 'Missing required column `' + columnToMiss + '`';
                    }).join('; ');
                    testHelper.executeQuery(createInvalidDeprecatedFunctionQuery(fnName, tableColumns), function(err) {
                        assert.ifError(err);
                        var def = invalidSchemaDeprecatedSqlFnDefinition(fnName, buster);
                        testHelper.createAnalyses(def, function(err) {
                            assert.ok(err);
                            assert.equal(err.message, 'Validation failed: ' + expectedMessage + '.');
                            return done();
                        });
                    });
                });
            });
        });

        describe('invalid column types', function() {
            var fnName = 'DEP_EXT_test_deprecated_fn_invalid_type';

            afterEach(function(done) {
                testHelper.executeQuery(
                    'DROP FUNCTION ' + fnName + '(text, text[], numeric, text, text)',
                    done
                );
            });

            var invalidTypeScenarios = [
                {
                    column: 'cartodb_id',
                    expects: 'number',
                    tableColumns: {
                        'cartodb_id::text': 'cartodb_id text',
                        the_geom: 'the_geom geometry'
                    }
                },
                {
                    column: 'the_geom',
                    expects: 'geometry',
                    tableColumns: {
                        cartodb_id: 'cartodb_id numeric',
                        'ST_AsText(the_geom)': 'the_geom text'
                    }
                }
            ];
            invalidTypeScenarios.forEach(function(scenario, index) {
                it('should fail for ' + scenario.column + ' invalid column type', function (done) {
                    var createFnQuery = createInvalidDeprecatedFunctionQuery(fnName, scenario.tableColumns);
                    testHelper.executeQuery(createFnQuery, function(err) {
                        assert.ifError(err);
                        var def = invalidSchemaDeprecatedSqlFnDefinition(fnName, index);
                        testHelper.createAnalyses(def, function(err) {
                            assert.ok(err);
                            assert.equal(
                                err.message,
                                'Validation failed: Invalid type for column "' +
                                    scenario.column +
                                    '": expected `' + scenario.expects +
                                    '` got `string`.'
                            );
                            return done();
                        });
                    });
                });
            });
        });
    });

    describe('throw and catch errors', function() {

        var fnName = 'DEP_EXT_test_deprecated_fn_raising_error';

        before(function(done) {
            testHelper.executeQuery([
                'CREATE OR REPLACE FUNCTION ' + fnName + '(',
                '    query text, columns text[], buster numeric, table_name text, operation text',
                ')',
                'RETURNS VOID AS $$',
                '    BEGIN',
                '        IF buster < 0 THEN',
                '            RAISE EXCEPTION \'Invalid buster value=%\', buster',
                '                USING HINT = \'Please provide a positive value for buster\';',
                '        END IF;',
                '        IF operation = \'create\' THEN',
                '            EXECUTE \'CREATE TABLE \' || table_name || \' (',
                '                cartodb_id numeric, the_geom geometry, buster numeric',
                '            )\';',
                '        ELSIF operation = \'populate\' THEN',
                '            EXECUTE \'INSERT INTO \' || table_name || \' ',
                '                SELECT cartodb_id, the_geom, \' || buster || \'',
                '                FROM (\' || query || \') _q\';',
                '        END IF;',
                '    END;',
                '$$ LANGUAGE plpgsql;'
            ].join('\n'), done);
        });

        after(function(done) {
            testHelper.executeQuery(
                'DROP FUNCTION ' + fnName + '(text, text[], numeric, text, text)',
                done
            );
        });

        function invalidSchemaDeprecatedSqlFnDefinition(buster) {
            return {
                type: 'deprecated-sql-function',
                params: {
                    function_name: fnName,
                    primary_source: {
                        type: 'source',
                        params: {
                            query: QUERY_SOURCE
                        }
                    },
                    function_args: [buster]
                }
            };
        }

        it('should work for valid buster param', function (done) {
            testHelper.getResult(invalidSchemaDeprecatedSqlFnDefinition(1), function(err, rows) {
                assert.ok(!err, err);
                assert.equal(rows.length, 3);
                rows.forEach(function(row) {
                    assert.equal(row.buster, 1);
                });
                return done();
            });
        });

        it('should expose error raised from function', function (done) {
            testHelper.createAnalyses(invalidSchemaDeprecatedSqlFnDefinition(-1), function(err) {
                assert.ok(err);
                assert.equal(err.message, 'Invalid buster value=-1');
                assert.equal(err.hint, 'Please provide a positive value for buster');
                return done();
            });
        });
    });
});
<|endoftext|>


<commit_before>'use strict';

var fs		= require('fs');
var http	= require('http');
var exec	= require('child_process').exec;

var serialNumber = function (cb, cmdPrefix) {
	var delimiter = ': ';

	var fromCache = function (error, stdout) {
		fs.readFile(__dirname + '/cache', function (fsErr, data) {
			if (data) {data = data.toString().trim();}
			if (fsErr || !data || data.length < 2) {
				attemptEC2(function() {
					stdoutHandler(error, stdout, true);
				});
			} else {
				cb(null, data);
			}
		});
	};

	var stdoutHandler = function (error, stdout, bypassCache) {
		if (error && !bypassCache) {
			fromCache(error, stdout);
		} else {
			cb(error, parseResult(stdout));
		}
	};

	var parseResult = function (input) {
		return input.slice(input.indexOf(delimiter) + 2).trim();
	};

	var attemptEC2 = function (failCb) {
		var data = '';
		var failHandler = function () {
			failCb();
			failCb = function () {};
		};
		var request = http.get(
			'http://169.254.169.254/latest/meta-data/instance-id',
			function (res) {
				res.on('data', function (chunk) {
					data += chunk;
				}).on('end', function () {
					if (data.length > 2) {
						cb(null, data.trim());
					} else {
						failHandler();
					}
				});
			}
		);
		request.on('error', failHandler).setTimeout(1000, failHandler);
	};

	cmdPrefix = cmdPrefix || '';
	var vals = ['Serial', 'UUID'];
	var cmd;

	switch (process.platform) {

	case 'win32':
		delimiter = '\r\n';
		vals[0] = 'IdentifyingNumber';
		cmd = 'wmic csproduct get ';
		return;

	case 'darwin':
		cmd = 'system_profiler SPHardwareDataType | grep ';
		break;

	case 'linux':
	case 'freebsd':
		cmd = 'dmidecode -t system | grep ';
		break;
	}

	if (!cmd) return cb(new Error('Cannot provide serial number for ' + process.platform));

	exec(cmdPrefix + cmd + vals[0], function (error, stdout) {
		if (error || parseResult(stdout).length > 1) {
			stdoutHandler(error, stdout);
		} else {
			exec(cmdPrefix + cmd + vals[1], stdoutHandler);
		}
	});
};

module.exports = exports = serialNumber;

exports.useSudo = function (cb) {
	serialNumber(cb, 'sudo ');
};
<commit_msg>Add preferUUID option<commit_after>'use strict';

var fs		= require('fs');
var http	= require('http');
var exec	= require('child_process').exec;

var serialNumber = function (cb, cmdPrefix) {
	var delimiter = ': ';

	var fromCache = function (error, stdout) {
		fs.readFile(__dirname + '/cache', function (fsErr, data) {
			if (data) {data = data.toString().trim();}
			if (fsErr || !data || data.length < 2) {
				attemptEC2(function() {
					stdoutHandler(error, stdout, true);
				});
			} else {
				cb(null, data);
			}
		});
	};

	var stdoutHandler = function (error, stdout, bypassCache) {
		if (error && !bypassCache) {
			fromCache(error, stdout);
		} else {
			cb(error, parseResult(stdout));
		}
	};

	var parseResult = function (input) {
		return input.slice(input.indexOf(delimiter) + 2).trim();
	};

	var attemptEC2 = function (failCb) {
		var data = '';
		var failHandler = function () {
			failCb();
			failCb = function () {};
		};
		var request = http.get(
			'http://169.254.169.254/latest/meta-data/instance-id',
			function (res) {
				res.on('data', function (chunk) {
					data += chunk;
				}).on('end', function () {
					if (data.length > 2) {
						cb(null, data.trim());
					} else {
						failHandler();
					}
				});
			}
		);
		request.on('error', failHandler).setTimeout(1000, failHandler);
	};

	cmdPrefix = cmdPrefix || '';
	var vals = ['Serial', 'UUID'];
	var cmd;

	switch (process.platform) {

	case 'win32':
		delimiter = '\r\n';
		vals[0] = 'IdentifyingNumber';
		cmd = 'wmic csproduct get ';
		return;

	case 'darwin':
		cmd = 'system_profiler SPHardwareDataType | grep ';
		break;

	case 'linux':
	case 'freebsd':
		cmd = 'dmidecode -t system | grep ';
		break;
	}

	if (!cmd) return cb(new Error('Cannot provide serial number for ' + process.platform));

	if (serialNumber.preferUUID) vals.reverse();

	exec(cmdPrefix + cmd + vals[0], function (error, stdout) {
		if (error || parseResult(stdout).length > 1) {
			stdoutHandler(error, stdout);
		} else {
			exec(cmdPrefix + cmd + vals[1], stdoutHandler);
		}
	});
};

serialNumber.preferUUID = false;

module.exports = exports = serialNumber;

exports.useSudo = function (cb) {
	serialNumber(cb, 'sudo ');
};
<|endoftext|>


<commit_before>(function() {

var checkVersion = Dagaz.Model.checkVersion;

Dagaz.Model.checkVersion = function(design, name, value) {
  if (name != "chessball-extension") {
     checkVersion(design, name, value);
  }
}

var sign = function(x) {
  if (x < 0) return -1;
  if (x > 0) return 1;
  return 0;
}

var badDir = function(piece, dx, dy, nx, ny) {
  if ((dx == nx) && (dy == ny)) return false;
  if (piece.type == 2) {
      if ((dx == -nx) && (dy == -ny)) return false;
      if ((dx ==  dy) && (nx == -ny)) return false;
      if ((dx == -dy) && (nx ==  ny)) return false;
      if ((dx == 0) && (ny == 0)) return false;
      if ((dy == 0) && (nx == 0)) return false;
  }
  if (piece.type == 3) {
      if ((dx == 0) && (dy < 0)) {
         if ((nx < 0) && (ny < 0)) return false;
         if ((nx > 0) && (ny < 0)) return false;
      }
      if ((dx > 0) && (dy == 0)) {
         if ((nx > 0) && (ny < 0)) return false;
         if ((nx > 0) && (ny > 0)) return false;
      }
      if ((dx == 0) && (dy > 0)) {
         if ((nx > 0) && (ny > 0)) return false;
         if ((nx < 0) && (ny > 0)) return false;
      }
      if ((dx < 0) && (dy == 0)) {
         if ((nx < 0) && (ny > 0)) return false;
         if ((nx < 0) && (ny < 0)) return false;
      }
      if ((dx < 0) && (dy < 0)) {
         if ((nx < 0) && (ny == 0)) return false;
         if ((nx == 0) && (ny < 0)) return false;
      }
      if ((dx > 0) && (dy < 0)) {
         if ((nx == 0) && (ny < 0)) return false;
         if ((nx > 0) && (ny == 0)) return false;
      }
      if ((dx > 0) && (dy > 0)) {
         if ((nx > 0) && (ny == 0)) return false;
         if ((nx == 0) && (ny > 0)) return false;
      }
      if ((dx < 0) && (dy > 0)) {
         if ((nx == 0) && (ny > 0)) return false;
         if ((nx < 0) && (ny == 0)) return false;
      }
  }
  return true;
}

var CheckInvariants = Dagaz.Model.CheckInvariants;

Dagaz.Model.CheckInvariants = function(board) {
  var design = Dagaz.Model.design;
  _.each(board.moves, function(move) {
      if ((move.actions.length > 0) && (move.actions[0][0] !== null) && (move.actions[0][1] !== null)) {
           var pos = move.actions[0][0][0];
           var piece = board.getPiece(pos);
           if (piece !== null) {
               p = move.actions[0][1][0];
               if (piece.type > 1) {
                   if (design.inZone(0, 1, p) || design.inZone(0, 2, p)) {
                       move.failed = true;
                       return;
                   }
               }
               var target = board.getPiece(p);
               if ((move.actions.length > 1) && (target !== null) && (target.type == 0)) {
                   move.dropPiece(p, piece, 2);
                   var dx = sign(Dagaz.Model.getX(p) - Dagaz.Model.getX(pos));
                   var dy = sign(Dagaz.Model.getY(p) - Dagaz.Model.getY(pos));
                   pos = move.actions[1][1][0];
                   var nx = sign(Dagaz.Model.getX(pos) - Dagaz.Model.getX(p));
                   var ny = sign(Dagaz.Model.getY(pos) - Dagaz.Model.getY(p));
                   if (badDir(piece, dx, dy, nx, ny)) {
                       move.failed = true;
                       return;
                   }
               }
           }
      }
  });
  CheckInvariants(board);
}

})();
<commit_msg>Chessball (fixed)<commit_after>(function() {

var checkVersion = Dagaz.Model.checkVersion;

Dagaz.Model.checkVersion = function(design, name, value) {
  if (name != "chessball-extension") {
     checkVersion(design, name, value);
  }
}

var sign = function(x) {
  if (x < 0) return -1;
  if (x > 0) return 1;
  return 0;
}

var badDir = function(piece, dx, dy, nx, ny) {
  if ((dx == nx) && (dy == ny)) return false;
  if (piece.type == 2) {
      if ((dx == -nx) && (dy == -ny)) return false;
      if ((dx ==  dy) && (nx == -ny)) return false;
      if ((dx == -dy) && (nx ==  ny)) return false;
      if ((dx == 0) && (ny == 0)) return false;
      if ((dy == 0) && (nx == 0)) return false;
  }
  if (piece.type == 3) {
      if ((dx == 0) && (dy < 0)) {
         if ((nx < 0) && (ny < 0)) return false;
         if ((nx > 0) && (ny < 0)) return false;
      }
      if ((dx > 0) && (dy == 0)) {
         if ((nx > 0) && (ny < 0)) return false;
         if ((nx > 0) && (ny > 0)) return false;
      }
      if ((dx == 0) && (dy > 0)) {
         if ((nx > 0) && (ny > 0)) return false;
         if ((nx < 0) && (ny > 0)) return false;
      }
      if ((dx < 0) && (dy == 0)) {
         if ((nx < 0) && (ny > 0)) return false;
         if ((nx < 0) && (ny < 0)) return false;
      }
      if ((dx < 0) && (dy < 0)) {
         if ((nx < 0) && (ny == 0)) return false;
         if ((nx == 0) && (ny < 0)) return false;
      }
      if ((dx > 0) && (dy < 0)) {
         if ((nx == 0) && (ny < 0)) return false;
         if ((nx > 0) && (ny == 0)) return false;
      }
      if ((dx > 0) && (dy > 0)) {
         if ((nx > 0) && (ny == 0)) return false;
         if ((nx == 0) && (ny > 0)) return false;
      }
      if ((dx < 0) && (dy > 0)) {
         if ((nx == 0) && (ny > 0)) return false;
         if ((nx < 0) && (ny == 0)) return false;
      }
  }
  return true;
}

var CheckInvariants = Dagaz.Model.CheckInvariants;

Dagaz.Model.CheckInvariants = function(board) {
  var design = Dagaz.Model.design;
  _.each(board.moves, function(move) {
      if ((move.actions.length > 0) && (move.actions[0][0] !== null) && (move.actions[0][1] !== null)) {
           var pos = move.actions[0][0][0];
           var piece = board.getPiece(pos);
           if (piece !== null) {
               p = move.actions[0][1][0];
               if (piece.type > 1) {
                   if (design.inZone(0, 1, p) || design.inZone(0, 2, p)) {
                       move.failed = true;
                       return;
                   }
               }
               var target = board.getPiece(p);
               if ((target !== null) && (target.type == 0)) {
                   if (move.actions.length < 2) {
                       move.failed = true;
                       return;
                   }
                   move.dropPiece(p, piece, 2);
                   var dx = sign(Dagaz.Model.getX(p) - Dagaz.Model.getX(pos));
                   var dy = sign(Dagaz.Model.getY(p) - Dagaz.Model.getY(pos));
                   pos = move.actions[1][1][0];
                   var nx = sign(Dagaz.Model.getX(pos) - Dagaz.Model.getX(p));
                   var ny = sign(Dagaz.Model.getY(pos) - Dagaz.Model.getY(p));
                   if (badDir(piece, dx, dy, nx, ny)) {
                       move.failed = true;
                       return;
                   }
               }
           }
      }
  });
  CheckInvariants(board);
}

})();
<|endoftext|>


<commit_before>// QHOME=./q ./q/m32/q -p 5000
// q)\l tick/u.q
// q)trade: ([] sym:`$(); time:`timestamp$())
// q)trade,:(`a;.z.p)
// q).u.init[]
// q).z.ts: {[] .u.pub[`trade;trade]}
// q)\t 500

var nodeq = require("../index.js"),
	assert = require("assert-plus");

describe("subs", function() {
	"use strict";
	var con;
	before(function(done) {
		nodeq.connect({host: "localhost", port: 5000}, function(err, c) {
			if (err) { throw err; }
			con = c;
			done();
		});
	});
	after(function(done) {
		con.close(function() {
			con = undefined;
			done();
		});
	});
	it("subscribe all", function(done) {
		con.once("upd", function(table, data) {
			assert.string(table, "table");
			assert.arrayOfObject(data, "data");
			done();
		});
		con.ks(".u.sub[`;`]", function(err) {
			if (err) { throw err; }
		});
	});
});
<commit_msg>improved subscription integration test<commit_after>// QHOME=./q ./q/m32/q -p 5000
var nodeq = require("../index.js"),
	async = require("async"),
	assert = require("assert-plus");

describe("subs", function() {
	"use strict";
	var con;
	before(function(done) {
		nodeq.connect({host: "localhost", port: 5000}, function(err, c) {
			if (err) { throw err; }
			async.each([
				'system "l tick/u.q"',
				"trade: ([] sym:`$(); time:`timestamp$())",
				".u.init[]",
				".z.ts: {[] trade::0#trade; trade,::(`a;.z.p); .u.pub[`trade;trade]}",
				'system "t 500"'
			], function(q, cb) {
				c.k(q, cb);
			}, function(err) {
				if (err) { throw err; }
				con = c;
				done();
			});
		});
	});
	after(function(done) {
		con.close(function() {
			con = undefined;
			done();
		});
	});
	it("subscribe all", function(done) {
		con.once("upd", function(table, data) {
			assert.string(table, "table");
			assert.arrayOfObject(data, "data");
			done();
		});
		con.ks(".u.sub[`;`]", function(err) {
			if (err) { throw err; }
		});
	});
});
<|endoftext|>


<commit_before>/**
 * CPU Usage
 *
 * Set all action for CPU Usage chart
 * (code standards: http://javascript.crockford.com/code.html)
 */

var cpuUsageInterval = {};  // set interval globally

/**
 * Get new data via ajax call and set it to given serie
 */
function requestCpuUsageChartData(series, csrf, server, secret, interval) {
    var address = '/ajax/server/' + server + '/metrics/cpu_usage/'; // ajax call adress

    $.ajax({
        url: address,
        type: 'POST',
        dataType: 'json',
        headers: {
            'X-CSRFToken': csrf
        },
        cache: false,
        data: {
            'server': server,
            'secret': secret,
            'interval': interval
        },
        success: function(data) {
            series.addPoint(data[0], true, true);   // add new point to chart serie
        },
        error: function(data, textStatus, errorThrown) {
            // console.log('error: ' + textStatus);
            // console.log('error: ' + errorThrown);
        }
    });
}

/**
 * Call or stop interval update action (via parameter updateChart parameter)
 */
function updateCpuUsageChart(series, csrf, server, secret, interval, duration, updateChart) {
    if (updateChart) {
        cpuUsageInterval = setInterval(function () {    // start update by duration
            requestCpuUsageChartData(series, csrf, server, secret, interval)    // update chart data
        }, duration);
    } else {
        window.clearInterval(cpuUsageInterval);         // stop current interval
    }
}

/**
 * Display given chart with actual data
 */
function displayCpuUsageChart(chart, csrf, server, secret, interval) {
    var address = '/ajax/server/' + server + '/metrics/cpu_usage/'; // ajax call adress

    $.ajax({    // ajax for actual chart data
        url: address,
        type: 'POST',
        dataType: 'json',
        headers: {
            'X-CSRFToken': csrf
        },
        cache: false,
        data: {
            'server': server,
            'secret': secret,
            'interval': interval
        },
        success: function(data) {
            if (data != null) {                 // check data and fill if needed
                var optionalData = [],          // empty optional data array
                    optionalLength = 55,        // optional data lenght
                    dataLength = data.length;   // current data lenght

                for (var i = 0; i < (optionalLength - dataLength); i++) {
                    optionalData.push([
                        (data[0][0] - (i + 1) * 3), // set timestamp value
                        null                        // set cpu usage value
                    ]);
                }

                optionalData = optionalData.concat(data);   // fill data to optional lenght
                chart.series[0].setData(data.reverse());    // chart need reverted data
            }
        },
        error: function(data, textStatus, errorThrown) {
            // console.log('error: ' + textStatus);
            // console.log('error: ' + errorThrown);
        }
    });
}

/**
 * Set duration by interval value
 */
function setDuration(interval) {
    var duration = '3000';      // base duration value
    if (interval == '15m') {
        duration = '15000';
    } else if (interval == '1h') {
        duration = '60000';
    }
    return duration;
}

$(function () {
    $(document).ready(function () {
        var server = $('input[name="hwaddr"]').val(),           // server identifier
            csrf = $('input[name="csrfmiddlewaretoken"]').val(),// request middlevare secure
            secret = $('input[name="secret"]').val(),           // request authenticate
            interval = '3m';                                    // base interval setting

        Highcharts.setOptions({ // set global chart options
            global: {
                useUTC: false   // set UTC by TSDB setting
            }
        });

        var cpuUsageChart = new Highcharts.Chart({  // create chart object
            chart: {
                renderTo: 'cpu_usage',
                events: {
                    load: function() {
                        updateCpuUsageChart(    // set chart first draw update action
                            this.series[0],
                            csrf,
                            server,
                            secret,
                            interval,
                            setDuration(interval),
                            true
                       );
                    }
                }
            },
            title: {
                text: ''
            },
            xAxis: {
                type: 'datetime',
                labels: {
                    formatter: function() {
                        return Highcharts.dateFormat('%H:%M:%S', this.value * 1000);    // chart need value in milisecond
                    }
                }
            },
            yAxis: {
                title: {
                    text: 'CPU Usage value %'
                },
                plotLines: [{
                    value: 0,
                    width: 1,
                    color: '#808080'
                }]
            },
            tooltip: {
                formatter: function () {
                    return '' + Highcharts.numberFormat(this.y, 0)
                        + this.series.name + '
'
                        + Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', this.x*1000);

                }
            },
            legend: {
                enabled: false
            },
            exporting: {
                enabled: false
            },
            series: [{
                name: '% CPU used',
                data: [],
                zones: [    // set zones color setting
                    {
                        value: 10,
                        color: '#7cb5ec'
                    },
                    {
                        value: 80,
                        color: '#90ed7d'
                    },
                    {
                        value: 95,
                        color: 'orange'
                    },
                    {
                        color: 'red'
                    }
                ]
            }]
        });

        $('#cpu_usage_interval a').on('click', function() { // catch interval change action
            var link = this,                                // create current object
                interval = $(link).attr('data-interval'),   // get interval from data attribute
                duration = setDuration(interval);           // set duration

            updateCpuUsageChart(    // stop last ajax chart update
                cpuUsageChart.series[0],
                csrf,
                server,
                secret,
                interval,
                duration,
                false
            );

            displayCpuUsageChart(   // display chart with new interval
                cpuUsageChart,
                csrf,
                server,
                secret,
                interval,
                duration
            );

            updateCpuUsageChart(    // call new interval chart ajax update
                cpuUsageChart.series[0],
                csrf,
                server,
                secret,
                interval,
                duration,
                true
            );
        });

        displayCpuUsageChart(       // draw first chart
            cpuUsageChart,
            csrf,
            server,
            secret,
            interval,
            setDuration(interval)
        );
    });
});
<commit_msg>Consolidated ajax call in cpu usage chart<commit_after>/**
 * CPU Usage
 *
 * Set all action for CPU Usage chart
 * (code standards: http://javascript.crockford.com/code.html)
 */

var cpuUsageInterval = {},  // set interval globally
    optionalLength = 55;    // set optional data lenght globally

/**
 * Get new data via ajax call and set it to given serie
 */
function requestCpuUsageChartData(series, csrf, server, secret, interval, updateChart) {
    var address = '/ajax/server/' + server + '/metrics/cpu_usage/'; // ajax call adress

    $.ajax({
        url: address,
        type: 'POST',
        dataType: 'json',
        headers: {
            'X-CSRFToken': csrf
        },
        cache: false,
        data: {
            'server': server,
            'secret': secret,
            'interval': interval
        },
        success: function(data) {
            if (updateChart) {
                series.addPoint(data[0], true, true);   // add new point to chart serie
            } else {
                series.setData(addFirstChartData(data)) // add data set to chart serie
            }
        },
        error: function(data, textStatus, errorThrown) {
            // console.log('error: ' + textStatus);
            // console.log('error: ' + errorThrown);
        }
    });
}

/**
 * Check, fill and sort given data with global parametrs
 */
function addFirstChartData(data) {
    var optionalData = [],          // empty optional data array
        dataLength = data.length;   // current data lenght

    for (var i = 0; i < (optionalLength - dataLength); i++) {
        optionalData.push([
            (data[0][0] - (i + 1) * 3), // set timestamp value
            null                        // set cpu usage value
        ]);
    }

    optionalData = optionalData.concat(data);   // fill data to optional lenght
    return data.reverse();          // chart need reverted data
}

/**
 * Call or stop interval update action (via parameter updateChart parameter)
 */
function updateCpuUsageChart(series, csrf, server, secret, interval, duration, updateChart) {
    if (updateChart) {
        cpuUsageInterval = setInterval(function () {    // start update by duration
            requestCpuUsageChartData(series, csrf, server, secret, interval, true)    // update chart data
        }, duration);
    } else {
        window.clearInterval(cpuUsageInterval);         // stop current interval
    }
}

/**
 * Display given chart with actual data
 */
function displayCpuUsageChart(chart, csrf, server, secret, interval) {
    requestCpuUsageChartData(   // add new data to selected chart series
        chart.series[0],
        csrf,
        server,
        secret,
        interval,
        false
    );
}

/**
 * Set duration by interval value
 */
function setDuration(interval) {
    var duration = '3000';      // base duration value
    if (interval == '15m') {
        duration = '15000';
    } else if (interval == '1h') {
        duration = '60000';
    }
    return duration;
}

$(function () {
    $(document).ready(function () {
        var server = $('input[name="hwaddr"]').val(),           // server identifier
            csrf = $('input[name="csrfmiddlewaretoken"]').val(),// request middlevare secure
            secret = $('input[name="secret"]').val(),           // request authenticate
            interval = '3m';                                    // base interval setting

        Highcharts.setOptions({ // set global chart options
            global: {
                useUTC: false   // set UTC by TSDB setting
            }
        });

        var cpuUsageChart = new Highcharts.Chart({  // create chart object
            chart: {
                renderTo: 'cpu_usage',
                events: {
                    load: function() {
                        updateCpuUsageChart(    // set chart first draw update action
                            this.series[0],
                            csrf,
                            server,
                            secret,
                            interval,
                            setDuration(interval),
                            true
                       );
                    }
                }
            },
            title: {
                text: ''
            },
            xAxis: {
                type: 'datetime',
                labels: {
                    formatter: function() {
                        return Highcharts.dateFormat('%H:%M:%S', this.value * 1000);    // chart need value in milisecond
                    }
                }
            },
            yAxis: {
                title: {
                    text: 'CPU Usage value %'
                },
                plotLines: [{
                    value: 0,
                    width: 1,
                    color: '#808080'
                }]
            },
            tooltip: {
                formatter: function () {
                    return '' + Highcharts.numberFormat(this.y, 0)
                        + this.series.name + '
'
                        + Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', this.x*1000);

                }
            },
            legend: {
                enabled: false
            },
            exporting: {
                enabled: false
            },
            series: [{
                name: '% CPU used',
                data: [],
                zones: [    // set zones color setting
                    {
                        value: 10,
                        color: '#7cb5ec'
                    },
                    {
                        value: 80,
                        color: '#90ed7d'
                    },
                    {
                        value: 95,
                        color: 'orange'
                    },
                    {
                        color: 'red'
                    }
                ]
            }]
        });

        $('#cpu_usage_interval a').on('click', function() { // catch interval change action
            var link = this,                                // create current object
                interval = $(link).attr('data-interval'),   // get interval from data attribute
                duration = setDuration(interval);           // set duration

            updateCpuUsageChart(    // stop last ajax chart update
                cpuUsageChart.series[0],
                csrf,
                server,
                secret,
                interval,
                duration,
                false
            );

            displayCpuUsageChart(   // display chart with new interval
                cpuUsageChart,
                csrf,
                server,
                secret,
                interval,
                duration
            );

            updateCpuUsageChart(    // call new interval chart ajax update
                cpuUsageChart.series[0],
                csrf,
                server,
                secret,
                interval,
                duration,
                true
            );
        });

        displayCpuUsageChart(       // draw first chart
            cpuUsageChart,
            csrf,
            server,
            secret,
            interval,
            setDuration(interval)
        );
    });
});
<|endoftext|>


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

  var prop = function(initialValue) {
    var cache = initialValue;
    return function(value) {
      if (typeof value === 'undefined')
        return cache;
      cache = value;
      markDirty(this);
    };
  };

  var markDirty = (function() {
    var dirtyComponents = [];
    var requestId = null;
    var callback = function() {
      dirtyComponents.forEach(function(component) {
        component.redraw();
      });
      dirtyComponents = [];
      requestId = null;
    };
    return function(component) {
      if (typeof document === 'undefined')
        return;
      if (dirtyComponents.indexOf(component) === -1)
        dirtyComponents.push(component);
      if (requestId !== null)
        return;
      requestId = dom.animate(callback);
    };
  })();

  var diffObj = function(newObj, oldObj) {
    var diff = {};
    for (var key in newObj) {
      if (newObj[key] !== oldObj[key])
        diff[key] = newObj[key];
    }
    return diff;
  };

  var dom = {};

  dom.el = function(selector) {
    if (selector[0] === '<') {
      selector = selector.match(/<(.+)>/)[1];
      return document.createElement(selector);
    }
    return document.querySelector(selector);
  };

  dom.css = function(el, props) {
    var style = el.style;
    for (var prop in props) {
      style[prop] = props[prop];
    }
  };

  dom.append = function(parent, el) {
    parent.appendChild(el);
  };

  dom.remove = function(el) {
    el.parentNode.removeChild(el);
  };

  dom.text = function(el, s) {
    el.textContent = s;
  };

  dom.html = function(el, s) {
    el.innerHTML = s;
  };

  dom.animate = function(callback) {
    return window.requestAnimationFrame(callback);
  };

  var Node = function(option) {
    this.content = prop(option.content || '');
    this.contentType = prop(option.contentType || Node.CONTENT_TYPE_TEXT);
    this.x = prop(option.x || 0);
    this.y = prop(option.y || 0);
    this.width = prop(option.width || 75);
    this.height = prop(option.height || 30);
    this.backgroundColor = prop(option.backgroundColor || '#a7cbe6');
    this.borderColor = prop(option.borderColor || '#333');
    this.borderWidth = prop(option.borderWidth || 2);
    this.textColor = prop(option.textColor || '#333');
    this.element = prop(null);
    this.parentElement = prop(null);
    this.cache = prop({});
  };

  Node.prototype.text = function(text) {
    if (typeof text === 'undefined')
      return this.content();
    this.content(text);
    this.contentType(Node.CONTENT_TYPE_TEXT);
  };

  Node.prototype.html = function(html) {
    if (typeof html === 'undefined')
      return this.content();
    this.content(html);
    this.contentType(Node.CONTENT_TYPE_HTML);
  };

  Node.prototype.style = function() {
    var contentType = this.contentType();
    var lineHeight = (contentType === Node.CONTENT_TYPE_TEXT) ? this.height() : 14;
    var textAlign = (contentType === Node.CONTENT_TYPE_TEXT) ? 'center' : 'left';
    var translate = 'translate(' + this.x() + 'px, ' + this.y() + 'px)';
    return {
      backgroundColor: this.backgroundColor(),
      border: this.borderWidth() + 'px solid ' + this.borderColor(),
      borderRadius: '4px',
      color: this.textColor(),
      height: this.height() + 'px',
      lineHeight: lineHeight + 'px',
      MozTransform: translate,
      msTransform: translate,
      overflow: 'hidden',
      position: 'absolute',
      textAlign: textAlign,
      textOverflow: 'ellipsis',
      transform: translate,
      webkitTransform: translate,
      whiteSpace: 'nowrap',
      width: this.width() + 'px'
    };
  };

  Node.prototype.redraw = function() {
    var content = this.content();
    var contentType = this.contentType();
    var style = this.style();
    var element = this.element();
    var parentElement = this.parentElement();
    var cache = this.cache();

    // add element
    if (parentElement && !element) {
      element = dom.el('<div>');
      this.element(element);
      this.redraw();
      dom.append(parentElement, element);
      return;
    }

    // remove element
    if (!parentElement && element) {
      dom.remove(element);
      this.element(null);
      this.cache({});
      return;
    }

    if (content !== cache.content) {
      if (contentType === Node.CONTENT_TYPE_TEXT)
        dom.text(element, content);
      else if (contentType === Node.CONTENT_TYPE_HTML)
        dom.html(element, content);
      cache.content = content;
    }

    var diffStyle = diffObj(style, cache.style || {});
    dom.css(element, diffStyle);
    cache.style = style;
  };

  Node.CONTENT_TYPE_TEXT = 'text';
  Node.CONTENT_TYPE_HTML = 'html';

  var Link = function(option) {
    this.text = prop(option.text || '');
    this.source = prop(option.source || null);
    this.target = prop(option.target || null);
    this.element = prop(null);
    this.parentElement = prop(null);
  };

  Link.prototype.redraw = function() {
    var element = this.element();
    var parentElement = this.parentElement();

    // add element
    if (parentElement && !element) {
      element = dom.el('<div>');
      this.element(element);
      dom.append(parentElement, element);
      return;
    }

    // remove element
    if (!parentElement && element) {
      dom.remove(element);
      this.element(null);
      this.cache({});
      return;
    }
  };

  var Cmap = function(element) {
    if (!(this instanceof Cmap))
      return new Cmap(element);

    this.nodeList = prop([]);
    this.linkList = prop([]);
    this.element = prop(element);

    markDirty(this);
  };

  Cmap.prototype.createNode = function(option) {
    return new Node(option || {});
  };

  Cmap.prototype.createLink = function(option) {
    return new Link(option || {});
  };

  Cmap.prototype.add = function(child) {
    var list;

    if (child instanceof Node)
      list = this.nodeList();
    else if (child instanceof Link)
      list = this.linkList();
    else
      return;

    var index = list.indexOf(child);
    if (index === -1)
      list.push(child);

    child.parentElement(this.element());
  };

  Cmap.prototype.remove = function(child) {
    var list;

    if (child instanceof Node)
      list = this.nodeList();
    else if (child instanceof Link)
      list = this.linkList();
    else
      return;

    var index = list.indexOf(child);
    if (index !== -1)
      list.splice(index, 1);

    child.parentElement(null);
  };

  Cmap.prototype.style = function() {
    return {
      color: '#333',
      cursor: 'default',
      fontFamily: 'sans-serif',
      fontSize: '14px',
      MozUserSelect: 'none',
      msUserSelect: 'none',
      position: 'relative',
      userSelect: 'none',
      webkitUserSelect: 'none'
    };
  };

  Cmap.prototype.redraw = function() {
    var element = this.element();

    if (!element) {
      element = dom.el('<div>');
      this.element(element);
    }

    dom.css(element, this.style());
  };

  if (typeof module !== 'undefined' && module.exports)
    module.exports = Cmap;
  else
    global.Cmap = Cmap;
})(this);<commit_msg>Refactor code for getting a list of node/link<commit_after>(function(global) {
  'use strict';

  var prop = function(initialValue) {
    var cache = initialValue;
    return function(value) {
      if (typeof value === 'undefined')
        return cache;
      cache = value;
      markDirty(this);
    };
  };

  var markDirty = (function() {
    var dirtyComponents = [];
    var requestId = null;
    var callback = function() {
      dirtyComponents.forEach(function(component) {
        component.redraw();
      });
      dirtyComponents = [];
      requestId = null;
    };
    return function(component) {
      if (typeof document === 'undefined')
        return;
      if (dirtyComponents.indexOf(component) === -1)
        dirtyComponents.push(component);
      if (requestId !== null)
        return;
      requestId = dom.animate(callback);
    };
  })();

  var diffObj = function(newObj, oldObj) {
    var diff = {};
    for (var key in newObj) {
      if (newObj[key] !== oldObj[key])
        diff[key] = newObj[key];
    }
    return diff;
  };

  var dom = {};

  dom.el = function(selector) {
    if (selector[0] === '<') {
      selector = selector.match(/<(.+)>/)[1];
      return document.createElement(selector);
    }
    return document.querySelector(selector);
  };

  dom.css = function(el, props) {
    var style = el.style;
    for (var prop in props) {
      style[prop] = props[prop];
    }
  };

  dom.append = function(parent, el) {
    parent.appendChild(el);
  };

  dom.remove = function(el) {
    el.parentNode.removeChild(el);
  };

  dom.text = function(el, s) {
    el.textContent = s;
  };

  dom.html = function(el, s) {
    el.innerHTML = s;
  };

  dom.animate = function(callback) {
    return window.requestAnimationFrame(callback);
  };

  var Node = function(option) {
    this.content = prop(option.content || '');
    this.contentType = prop(option.contentType || Node.CONTENT_TYPE_TEXT);
    this.x = prop(option.x || 0);
    this.y = prop(option.y || 0);
    this.width = prop(option.width || 75);
    this.height = prop(option.height || 30);
    this.backgroundColor = prop(option.backgroundColor || '#a7cbe6');
    this.borderColor = prop(option.borderColor || '#333');
    this.borderWidth = prop(option.borderWidth || 2);
    this.textColor = prop(option.textColor || '#333');
    this.element = prop(null);
    this.parentElement = prop(null);
    this.cache = prop({});
  };

  Node.prototype.text = function(text) {
    if (typeof text === 'undefined')
      return this.content();
    this.content(text);
    this.contentType(Node.CONTENT_TYPE_TEXT);
  };

  Node.prototype.html = function(html) {
    if (typeof html === 'undefined')
      return this.content();
    this.content(html);
    this.contentType(Node.CONTENT_TYPE_HTML);
  };

  Node.prototype.style = function() {
    var contentType = this.contentType();
    var lineHeight = (contentType === Node.CONTENT_TYPE_TEXT) ? this.height() : 14;
    var textAlign = (contentType === Node.CONTENT_TYPE_TEXT) ? 'center' : 'left';
    var translate = 'translate(' + this.x() + 'px, ' + this.y() + 'px)';
    return {
      backgroundColor: this.backgroundColor(),
      border: this.borderWidth() + 'px solid ' + this.borderColor(),
      borderRadius: '4px',
      color: this.textColor(),
      height: this.height() + 'px',
      lineHeight: lineHeight + 'px',
      MozTransform: translate,
      msTransform: translate,
      overflow: 'hidden',
      position: 'absolute',
      textAlign: textAlign,
      textOverflow: 'ellipsis',
      transform: translate,
      webkitTransform: translate,
      whiteSpace: 'nowrap',
      width: this.width() + 'px'
    };
  };

  Node.prototype.redraw = function() {
    var content = this.content();
    var contentType = this.contentType();
    var style = this.style();
    var element = this.element();
    var parentElement = this.parentElement();
    var cache = this.cache();

    // add element
    if (parentElement && !element) {
      element = dom.el('<div>');
      this.element(element);
      this.redraw();
      dom.append(parentElement, element);
      return;
    }

    // remove element
    if (!parentElement && element) {
      dom.remove(element);
      this.element(null);
      this.cache({});
      return;
    }

    if (content !== cache.content) {
      if (contentType === Node.CONTENT_TYPE_TEXT)
        dom.text(element, content);
      else if (contentType === Node.CONTENT_TYPE_HTML)
        dom.html(element, content);
      cache.content = content;
    }

    var diffStyle = diffObj(style, cache.style || {});
    dom.css(element, diffStyle);
    cache.style = style;
  };

  Node.CONTENT_TYPE_TEXT = 'text';
  Node.CONTENT_TYPE_HTML = 'html';

  var Link = function(option) {
    this.text = prop(option.text || '');
    this.source = prop(option.source || null);
    this.target = prop(option.target || null);
    this.element = prop(null);
    this.parentElement = prop(null);
  };

  Link.prototype.redraw = function() {
    var element = this.element();
    var parentElement = this.parentElement();

    // add element
    if (parentElement && !element) {
      element = dom.el('<div>');
      this.element(element);
      dom.append(parentElement, element);
      return;
    }

    // remove element
    if (!parentElement && element) {
      dom.remove(element);
      this.element(null);
      this.cache({});
      return;
    }
  };

  var Cmap = function(element) {
    if (!(this instanceof Cmap))
      return new Cmap(element);

    this.nodeList = prop([]);
    this.linkList = prop([]);
    this.element = prop(element);

    markDirty(this);
  };

  Cmap.prototype.createNode = function(option) {
    return new Node(option || {});
  };

  Cmap.prototype.createLink = function(option) {
    return new Link(option || {});
  };

  Cmap.prototype.add = function(child) {
    var list = this.getListOfChild(child);

    if (!list)
      return;

    var index = list.indexOf(child);
    if (index === -1)
      list.push(child);

    child.parentElement(this.element());
  };

  Cmap.prototype.remove = function(child) {
    var list = this.getListOfChild(child);

    if (!list)
      return;

    var index = list.indexOf(child);
    if (index !== -1)
      list.splice(index, 1);

    child.parentElement(null);
  };

  Cmap.prototype.getListOfChild = function(child) {
    if (child.constructor === Node)
      return this.nodeList();
    else if (child.constructor === Link)
      return this.linkList();
    else
      return null;
  };

  Cmap.prototype.style = function() {
    return {
      color: '#333',
      cursor: 'default',
      fontFamily: 'sans-serif',
      fontSize: '14px',
      MozUserSelect: 'none',
      msUserSelect: 'none',
      position: 'relative',
      userSelect: 'none',
      webkitUserSelect: 'none'
    };
  };

  Cmap.prototype.redraw = function() {
    var element = this.element();

    if (!element) {
      element = dom.el('<div>');
      this.element(element);
    }

    dom.css(element, this.style());
  };

  if (typeof module !== 'undefined' && module.exports)
    module.exports = Cmap;
  else
    global.Cmap = Cmap;
})(this);<|endoftext|>