Skip to content

Conversation

dinhtho
Copy link

@dinhtho dinhtho commented Feb 7, 2018

fix issue: #130
set height for dropdown list if dropdownStyle has height is auto.
New height based on height of dropdown button and quanlity of options in dropdown list.


const dropdownHeight = (this.props.dropdownStyle && StyleSheet.flatten(this.props.dropdownStyle).height) ||
StyleSheet.flatten(styles.dropdown).height;
const dropdownHeight = (this.props.dropdownStyle && StyleSheet.flatten(this.props.dropdownStyle).height);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't you change const to let? You changed the dropdownHeight in the following lines.

@maxhis
Copy link

maxhis commented Feb 8, 2018

@dinhtho Does it work on your side? I just tried your code, still not working as expected. - the dropdown menu not showing under status bar, but stretch quite tall. :(

@dinhtho
Copy link
Author

dinhtho commented Feb 9, 2018

Sorry @maxhis , that is a my mistake when I edit my code, you can see my code updated and now it works perfectly

@maxhis
Copy link

maxhis commented Feb 9, 2018

Thanks @dinhtho . But unfortunately, still not working on my side.

@dinhtho
Copy link
Author

dinhtho commented Feb 9, 2018

/**
 * Created by sohobloo on 16/9/13.
 */

'use strict';

import React, {
  Component,
} from 'react';

import {
  StyleSheet,
  Dimensions,
  View,
  Text,
  ListView,
  TouchableWithoutFeedback,
  TouchableNativeFeedback,
  TouchableOpacity,
  TouchableHighlight,
  Modal,
  ActivityIndicator,
} from 'react-native';

const PropTypes = require('prop-types');

const TOUCHABLE_ELEMENTS = ['TouchableHighlight', 'TouchableOpacity', 'TouchableWithoutFeedback', 'TouchableNativeFeedback'];

export default class ModalDropdown extends Component {
  static propTypes = {
    disabled: PropTypes.bool,
    scrollEnabled: PropTypes.bool,
    defaultIndex: PropTypes.number,
    defaultValue: PropTypes.string,
    options: PropTypes.array,

    accessible: PropTypes.bool,
    animated: PropTypes.bool,
    showsVerticalScrollIndicator: PropTypes.bool,
    keyboardShouldPersistTaps: PropTypes.string,

    style: PropTypes.oneOfType([PropTypes.number, PropTypes.object, PropTypes.array]),
    textStyle: PropTypes.oneOfType([PropTypes.number, PropTypes.object, PropTypes.array]),
    dropdownStyle: PropTypes.oneOfType([PropTypes.number, PropTypes.object, PropTypes.array]),
    dropdownTextStyle: PropTypes.oneOfType([PropTypes.number, PropTypes.object, PropTypes.array]),
    dropdownTextHighlightStyle: PropTypes.oneOfType([PropTypes.number, PropTypes.object, PropTypes.array]),

    adjustFrame: PropTypes.func,
    renderRow: PropTypes.func,
    renderSeparator: PropTypes.func,
    renderButtonText: PropTypes.func,

    onDropdownWillShow: PropTypes.func,
    onDropdownWillHide: PropTypes.func,
    onSelect: PropTypes.func
  };

  static defaultProps = {
    disabled: false,
    scrollEnabled: true,
    defaultIndex: -1,
    defaultValue: 'Please select...',
    options: null,
    animated: true,
    showsVerticalScrollIndicator: true,
    keyboardShouldPersistTaps: 'never'
  };

  constructor(props) {
    super(props);

    this._button = null;
    this._buttonFrame = null;
    this._nextValue = null;
    this._nextIndex = null;

    this.state = {
      disabled: props.disabled,
      accessible: !!props.accessible,
      loading: props.options === null || props.options === undefined,
      showDropdown: false,
      buttonText: props.defaultValue,
      selectedIndex: props.defaultIndex
    };
  }

  componentWillReceiveProps(nextProps) {
    let buttonText = this._nextValue == null ? this.state.buttonText : this._nextValue.toString();
    let selectedIndex = this._nextIndex == null ? this.state.selectedIndex : this._nextIndex;
    if (selectedIndex < 0) {
      selectedIndex = nextProps.defaultIndex;
      if (selectedIndex < 0) {
        buttonText = nextProps.defaultValue;
      }
    }
    this._nextValue = null;
    this._nextIndex = null;

    this.setState({
      disabled: nextProps.disabled,
      loading: nextProps.options == null,
      buttonText: buttonText,
      selectedIndex: selectedIndex
    });
  }

  render() {
    return (
      <View {...this.props}>
        {this._renderButton()}
        {this._renderModal()}
      </View>
    );
  }

  _updatePosition(callback) {
    if (this._button && this._button.measure) {
      this._button.measure((fx, fy, width, height, px, py) => {
        this._buttonFrame = {x: px, y: py, w: width, h: height};
        callback && callback();
      });
    }
  }

  show() {
    this._updatePosition(() => {
      this.setState({
        showDropdown: true
      });
    });
  }

  hide() {
    this.setState({
      showDropdown: false
    });
  }

  select(idx) {
    let value = this.props.defaultValue;
    if (idx == null || this.props.options == null || idx >= this.props.options.length) {
      idx = this.props.defaultIndex;
    }

    if (idx >= 0) {
      value = this.props.options[idx].toString();
    }

    this._nextValue = value;
    this._nextIndex = idx;

    this.setState({
      buttonText: value,
      selectedIndex: idx
    });
  }

  _renderButton() {
    return (
      <TouchableOpacity ref={button => this._button = button}
                        disabled={this.props.disabled}
                        accessible={this.props.accessible}
                        onPress={this._onButtonPress.bind(this)}>
        {
          this.props.children ||
          (
            <View style={styles.button}>
              <Text style={[styles.buttonText, this.props.textStyle]}
                    numberOfLines={1}>
                {this.state.buttonText}
              </Text>
            </View>
          )
        }
      </TouchableOpacity>
    );
  }

  _onButtonPress() {
    if (!this.props.onDropdownWillShow ||
      this.props.onDropdownWillShow() !== false) {
      this.show();
    }
  }

  _renderModal() {
    if (this.state.showDropdown && this._buttonFrame) {
      const frameStyle = this._calcPosition();
      const animationType = this.props.animated ? 'fade' : 'none';
      return (
        <Modal animationType={animationType}
               visible={true}
               transparent={true}
               onRequestClose={this._onRequestClose.bind(this)}
               supportedOrientations={['portrait', 'portrait-upside-down', 'landscape', 'landscape-left', 'landscape-right']}>
          <TouchableWithoutFeedback accessible={this.props.accessible}
                                    disabled={!this.state.showDropdown}
                                    onPress={this._onModalPress.bind(this)}>
            <View style={styles.modal}>
              <View style={[styles.dropdown, this.props.dropdownStyle, frameStyle]}>
                {this.state.loading ? this._renderLoading() : this._renderDropdown()}
              </View>
            </View>
          </TouchableWithoutFeedback>
        </Modal>
      );
    }
  }

  _calcPosition() {
    const dimensions = Dimensions.get('window');
    const windowWidth = dimensions.width;
    const windowHeight = dimensions.height;

     let dropdownHeight = (this.props.dropdownStyle && StyleSheet.flatten(this.props.dropdownStyle).height) ||
      StyleSheet.flatten(styles.dropdown).height;
      if (dropdownHeight == "auto") {
        let itemHeight = (this.props.style && StyleSheet.flatten(this.props.style).height);
        dropdownHeight = itemHeight * this.props.options.length;
      }

    const bottomSpace = windowHeight - this._buttonFrame.y - this._buttonFrame.h;
    const rightSpace = windowWidth - this._buttonFrame.x;
    const showInBottom = bottomSpace >= dropdownHeight || bottomSpace >= this._buttonFrame.y;
    const showInLeft = rightSpace >= this._buttonFrame.x;

    let style = {
      height: dropdownHeight,
      top: showInBottom ? this._buttonFrame.y + this._buttonFrame.h : Math.max(0, this._buttonFrame.y - dropdownHeight),
    };

    if (showInLeft) {
      style.left = this._buttonFrame.x;
    } else {
      const dropdownWidth = (this.props.dropdownStyle && StyleSheet.flatten(this.props.dropdownStyle).width) ||
        (this.props.style && StyleSheet.flatten(this.props.style).width) || -1;
      if (dropdownWidth !== -1) {
        style.width = dropdownWidth;
      }
      style.right = rightSpace - this._buttonFrame.w;
    }

    if (this.props.adjustFrame) {
      style = this.props.adjustFrame(style) || style;
    }

    return style;
  }

  _onRequestClose() {
    if (!this.props.onDropdownWillHide ||
      this.props.onDropdownWillHide() !== false) {
      this.hide();
    }
  }

  _onModalPress() {
    if (!this.props.onDropdownWillHide ||
      this.props.onDropdownWillHide() !== false) {
      this.hide();
    }
  }

  _renderLoading() {
    return (
      <ActivityIndicator size='small'/>
    );
  }

  _renderDropdown() {
    return (
      <ListView scrollEnabled={this.props.scrollEnabled}
                style={styles.list}
                dataSource={this._dataSource}
                renderRow={this._renderRow.bind(this)}
                renderSeparator={this.props.renderSeparator || this._renderSeparator.bind(this)}
                automaticallyAdjustContentInsets={false}
                showsVerticalScrollIndicator={this.props.showsVerticalScrollIndicator}
                keyboardShouldPersistTaps={this.props.keyboardShouldPersistTaps}
      />
    );
  }

  get _dataSource() {
    const ds = new ListView.DataSource({
      rowHasChanged: (r1, r2) => r1 !== r2
    });
    return ds.cloneWithRows(this.props.options);
  }

  _renderRow(rowData, sectionID, rowID, highlightRow) {
    const key = `row_${rowID}`;
    const highlighted = rowID == this.state.selectedIndex;
    const row = !this.props.renderRow ?
      (<Text style={[
        styles.rowText,
        this.props.dropdownTextStyle,
        highlighted && styles.highlightedRowText,
        highlighted && this.props.dropdownTextHighlightStyle
      ]}
      >
        {rowData}
      </Text>) :
      this.props.renderRow(rowData, rowID, highlighted);
    const preservedProps = {
      key: key,
      accessible: this.props.accessible,
      onPress: () => this._onRowPress(rowData, sectionID, rowID, highlightRow),
    };
    if (TOUCHABLE_ELEMENTS.find(name => name == row.type.displayName)) {
      const props = {...row.props};
      props.key = preservedProps.key;
      props.onPress = preservedProps.onPress;
      switch (row.type.displayName) {
        case 'TouchableHighlight':
        {
          return (
            <TouchableHighlight {...props}>
              {row.props.children}
            </TouchableHighlight>
          );
        }
        case 'TouchableOpacity':
        {
          return (
            <TouchableOpacity {...props}>
              {row.props.children}
            </TouchableOpacity>
          );
        }
        case 'TouchableWithoutFeedback':
        {
          return (
            <TouchableWithoutFeedback {...props}>
              {row.props.children}
            </TouchableWithoutFeedback>
          );
        }
        case 'TouchableNativeFeedback':
        {
          return (
            <TouchableNativeFeedback {...props}>
              {row.props.children}
            </TouchableNativeFeedback>
          );
        }
        default:
          break;
      }
    }
    return (
      <TouchableHighlight {...preservedProps}>
        {row}
      </TouchableHighlight>
    );
  }

  _onRowPress(rowData, sectionID, rowID, highlightRow) {
    const { onSelect, renderButtonText, onDropdownWillHide } = this.props;
    if (!onSelect || onSelect(rowID, rowData) !== false) {
      highlightRow(sectionID, rowID);
      this._nextValue = rowData;
      this._nextIndex = rowID;
      this.setState({
        buttonText: renderButtonText && renderButtonText(rowData) || rowData.toString(),
        selectedIndex: rowID
      });
    }
    if (!onDropdownWillHide || onDropdownWillHide() !== false) {
      this.setState({
        showDropdown: false
      });
    }
  }

  _renderSeparator(sectionID, rowID, adjacentRowHighlighted) {
    const key = `spr_${rowID}`;
    return (<View style={styles.separator}
                  key={key}
    />);
  }
}

const styles = StyleSheet.create({
  button: {
    justifyContent: 'center'
  },
  buttonText: {
    fontSize: 12
  },
  modal: {
    flexGrow: 1
  },
  dropdown: {
    position: 'absolute',
    height: (33 + StyleSheet.hairlineWidth) * 5,
    borderWidth: StyleSheet.hairlineWidth,
    borderColor: 'lightgray',
    borderRadius: 2,
    backgroundColor: 'white',
    justifyContent: 'center'
  },
  loading: {
    alignSelf: 'center'
  },
  list: {
    //flexGrow: 1,
  },
  rowText: {
    paddingHorizontal: 6,
    paddingVertical: 10,
    fontSize: 11,
    color: 'gray',
    backgroundColor: 'white',
    textAlignVertical: 'center'
  },
  highlightedRowText: {
    color: 'black'
  },
  separator: {
    height: StyleSheet.hairlineWidth,
    backgroundColor: 'lightgray'
  }
});

@dinhtho
Copy link
Author

dinhtho commented Feb 9, 2018

You can try ModalDropdown.js above
It is working for me
capture

@maxhis
Copy link

maxhis commented Feb 9, 2018

Tried it on both iOS and Android - still not working. :(

@dinhtho
Copy link
Author

dinhtho commented Feb 9, 2018

please comment your code here

@maxhis
Copy link

maxhis commented Feb 9, 2018

Here is the code snippet. BTW, I'm using react-navigation for screen navigation.

import React, { Component } from 'react';
import {StyleSheet, ScrollView, Text, SafeAreaView} from 'react-native';
import ModalDropdown from 'react-native-modal-dropdown';

export default class TestModalDropdown extends Component {

  static navigationOptions = {
    title: 'TestModal',
    tabBarVisible: false 
  }

  render() {
    return (
      <SafeAreaView style={styles.containerStyle}>
        <ScrollView>
          <Text style={styles.textStyle}>Hello world</Text>
          <Text style={styles.textStyle}>Hello world</Text>
          <Text style={styles.textStyle}>Hello world</Text>
          <ModalDropdown 
            options={['Fmale', 'Male']}
            dropdownStyle={styles.dropdownStyle}
          />
        </ScrollView>
      </SafeAreaView>
    );
  }
}

const styles = StyleSheet.create({
  containerStyle: {
    flex: 1,
    flexDirection: 'column',
    backgroundColor: 'white',
  },
  textStyle: {
    textAlign: 'center',
    fontSize: 18,
    height: 150,
  },
  dropdownStyle: {
    height: 'auto'
  }
});

@dinhtho
Copy link
Author

dinhtho commented Feb 9, 2018

@maxhis Your problem is here import ModalDropdown from 'react-native-modal-dropdown';
You used ModalDropdown from react-native-modal-dropdown library which has some issues.
You need to create ModalDropdown.js file from my code for yourseft and import to your project.
For example
import ModalDropdown from '/your directory/ModalDropdown '

@maxhis
Copy link

maxhis commented Feb 9, 2018

@dinhtho Actually, I modified the ModalDropdown.js under node_modules folder. Also tried to create a ModalDropdown.js file in my project as you said, still not solve the issue.

StyleSheet.flatten(styles.dropdown).height;
if (dropdownHeight == "auto") {
let itemHeight = (this.props.style && StyleSheet.flatten(this.props.style).height);
dropdownHeight = itemHeight * this.props.options.length;
Copy link
Owner

@sohobloo sohobloo Feb 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

possibly missing separator height in this way.
StyleSheet.flatten(this.props.style).height seems not the item height.

@sohobloo
Copy link
Owner

sohobloo commented Feb 9, 2018

I think we should only think about height:auto on position calculate because we can not easily get the item height when use renderRow and renderSeparator

@dinhtho
Copy link
Author

dinhtho commented Feb 9, 2018

@maxhis yes, I found my issue in my code when get itemHeight but i will be busy due to take a rest in lunar new year in my country so I cannot fix it right now,
I recommend you use my code to set specific value for dropdown list

const costFilter = ['All', 'Free', 'Split Cost', "I'll Pay"];

dropdownStyle={ { height: your itemHeight * costFilter.length }}

itemHeight is height of item you want to render on every row on dropdown list, you will resolve this problem with this trick :))

@dinhtho
Copy link
Author

dinhtho commented Feb 9, 2018

If you want to height of dropdown list as a auto height, you need set value for height of item on dropdown list,
It is so difficult to calculate height of dropdown list if height of item of dropdown list is not specific.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants