Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix bug: when height of dropdown list is auto #132

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open

fix bug: when height of dropdown list is auto #132

wants to merge 4 commits into from

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.

@@ -212,8 +212,12 @@ export default class ModalDropdown extends Component {
const windowWidth = dimensions.width;
const windowHeight = dimensions.height;

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.

None yet

3 participants