From 56903ebd0e1b38373f0de3735891d8d47d7e6cee Mon Sep 17 00:00:00 2001 From: Eero Bragge Date: Fri, 18 Mar 2016 12:18:46 +0200 Subject: [PATCH] feat(ReactPickerView): Add ReactPickerView ReactPicker provides access to native selector UI components for React Native JavaScript applications. - PR comments implemented Fixes #231 --- ReactNative.Tests/ReactNative.Tests.csproj | 1 - .../Views/Picker/ReactPickerTests.cs | 20 --- .../Views/Picker/ReactPickerManager.cs | 40 ++--- js/Components/Picker/PickerAndroid.js | 20 +++ js/Components/Picker/PickerIOS.js | 20 +++ js/Components/Picker/PickerWindows.js | 137 ++++++++++++++++++ 6 files changed, 193 insertions(+), 45 deletions(-) delete mode 100644 ReactNative.Tests/Views/Picker/ReactPickerTests.cs create mode 100644 js/Components/Picker/PickerAndroid.js create mode 100644 js/Components/Picker/PickerIOS.js create mode 100644 js/Components/Picker/PickerWindows.js diff --git a/ReactNative.Tests/ReactNative.Tests.csproj b/ReactNative.Tests/ReactNative.Tests.csproj index 1b9269c2756..f671724536d 100644 --- a/ReactNative.Tests/ReactNative.Tests.csproj +++ b/ReactNative.Tests/ReactNative.Tests.csproj @@ -144,7 +144,6 @@ UnitTestApp.xaml - diff --git a/ReactNative.Tests/Views/Picker/ReactPickerTests.cs b/ReactNative.Tests/Views/Picker/ReactPickerTests.cs deleted file mode 100644 index 2a24858f581..00000000000 --- a/ReactNative.Tests/Views/Picker/ReactPickerTests.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; -using Microsoft.VisualStudio.TestPlatform.UnitTestFramework.AppContainer; -using ReactNative.Views.Picker; -using ReactNative.UIManager; -using Windows.UI.Text; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; - -namespace ReactNative.Tests.Views.Picker -{ - [TestClass] - public class ReactPickerTests - { - [UITestMethod] - public void ReactPickerTests_Test() - { - - } - } -} diff --git a/ReactNative/Views/Picker/ReactPickerManager.cs b/ReactNative/Views/Picker/ReactPickerManager.cs index 305c4131df0..8e0b1467597 100644 --- a/ReactNative/Views/Picker/ReactPickerManager.cs +++ b/ReactNative/Views/Picker/ReactPickerManager.cs @@ -2,7 +2,6 @@ using ReactNative.UIManager; using ReactNative.UIManager.Events; using System; -using Windows.UI; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Media; @@ -14,12 +13,6 @@ namespace ReactNative.Views.Picker /// public class ReactPickerManager : BaseViewManager { - private const string PROP_COLOR = "color"; - private const string PROP_ENABLED = "enabled"; - private const string PROP_ITEMS = "items"; - private const string PROP_LABEL = "label"; - private const string PROP_SELECTED = "selected"; - private int _selected; /// @@ -41,7 +34,7 @@ public override string Name /// Set to true if the picker should be enabled, /// otherwise, set to false. /// - [ReactProperty(PROP_ENABLED)] + [ReactProperty("enabled")] public void SetEnabled(ComboBox view, bool enabled) { view.IsEnabled = enabled; @@ -52,16 +45,16 @@ public void SetEnabled(ComboBox view, bool enabled) /// /// a combobox instance. /// The selected item. - [ReactProperty(PROP_SELECTED)] + [ReactProperty("selected")] public void SetSelected(ComboBox view, int selected) { // Temporarily disable selection changed event handler. view.SelectionChanged -= OnSelectionChanged; - _selected = selected >= -1 ? selected : -1; - view.SelectedIndex = view.Items.Count > _selected ? _selected : view.Items.Count - 1; + _selected = selected; + view.SelectedIndex = view.Items.Count > _selected ? _selected : -1; - if (view.SelectedIndex != -1 ) + if (view.SelectedIndex != -1) { view.Foreground = ((ComboBoxItem)(view.Items[view.SelectedIndex])).Foreground; } @@ -74,30 +67,29 @@ public void SetSelected(ComboBox view, int selected) /// /// a combobox instance. /// The picker items. - [ReactProperty(PROP_ITEMS)] + [ReactProperty("items")] public void SetItems(ComboBox view, JArray items) { + // Temporarily disable selection changed event handler. view.SelectionChanged -= OnSelectionChanged; - for (int index = 0; index < items.Count; index++) + for (var index = 0; index < items.Count; index++) { - JToken token; - if ( (items[index] as JObject).TryGetValue(PROP_LABEL, out token) ) + JToken label; + if ((items[index] as JObject).TryGetValue("label", out label)) { var item = new ComboBoxItem(); JToken color; - item.Content = token.Value(); - if (((JObject)(items[index])).TryGetValue(PROP_COLOR, out color) ) + item.Content = label.Value(); + if ((color = items[index].Value("color")) != null) { - var rgb = color.Value(); - item.Foreground = new SolidColorBrush(Color.FromArgb((byte)((rgb >> 24) & 0xff), - (byte)((rgb >> 16) & 0xff), - (byte)((rgb >> 8) & 0xff), - (byte)(rgb & 0xff))); + var rgb = color.Value(); + item.Foreground = new SolidColorBrush(ColorHelpers.Parse(rgb)); } view.Items.Add(item); } - } + } + view.SelectedIndex = view.Items.Count > _selected ? _selected : -1; view.SelectionChanged += OnSelectionChanged; diff --git a/js/Components/Picker/PickerAndroid.js b/js/Components/Picker/PickerAndroid.js new file mode 100644 index 00000000000..f34abe43d81 --- /dev/null +++ b/js/Components/Picker/PickerAndroid.js @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule PickerAndroid + */ +'use strict'; + +var React = require('React'); + +var PickerAndroid = React.createClass({ + render: function() { + }, +}); + +module.exports = PickerAndroid; diff --git a/js/Components/Picker/PickerIOS.js b/js/Components/Picker/PickerIOS.js new file mode 100644 index 00000000000..d9ffc53bacf --- /dev/null +++ b/js/Components/Picker/PickerIOS.js @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule PickerIOS + */ +'use strict'; + +var React = require('React'); + +var PickerIOS = React.createClass({ + render: function() { + }, +}); + +module.exports = PickerIOS; diff --git a/js/Components/Picker/PickerWindows.js b/js/Components/Picker/PickerWindows.js new file mode 100644 index 00000000000..ad7b13f745c --- /dev/null +++ b/js/Components/Picker/PickerWindows.js @@ -0,0 +1,137 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule PickerWindows + * @flow + */ + +'use strict'; + +var ColorPropType = require('ColorPropType'); +var React = require('React'); +var ReactChildren = require('ReactChildren'); +var ReactPropTypes = require('ReactPropTypes'); +var StyleSheet = require('StyleSheet'); +var StyleSheetPropType = require('StyleSheetPropType'); +var View = require('View'); +var ViewStylePropTypes = require('ViewStylePropTypes'); + +var processColor = require('processColor'); +var requireNativeComponent = require('requireNativeComponent'); + +var REF_PICKER = 'picker'; + +var pickerStyleType = StyleSheetPropType({ + ...ViewStylePropTypes, + color: ColorPropType, +}); + +type Event = Object; + +/** + * Not exposed as a public API - use instead. + */ +var PickerWindows = React.createClass({ + + propTypes: { + ...View.propTypes, + style: pickerStyleType, + items: React.PropTypes.any, + selected: React.PropTypes.number, + selectedValue: React.PropTypes.any, + enabled: ReactPropTypes.bool, + onValueChange: ReactPropTypes.func, + prompt: ReactPropTypes.string, + testID: ReactPropTypes.string, + }, + + getInitialState: function() { + return this._stateFromProps(this.props); + }, + + componentWillReceiveProps: function(nextProps) { + this.setState(this._stateFromProps(nextProps)); + }, + + // Translate prop and children into stuff that the native picker understands. + _stateFromProps: function(props) { + var selectedIndex = 0; + let items = ReactChildren.map(props.children, (child, index) => { + if (child.props.value === props.selectedValue) { + selectedIndex = index; + } + let childProps = { + value: child.props.value, + label: child.props.label, + }; + if (child.props.color) { + childProps.color = processColor(child.props.color); + } + return childProps; + }); + return {selectedIndex, items}; + }, + + render: function() { + var Picker = ComboBoxPicker; + + var nativeProps = { + enabled: this.props.enabled, + items: this.state.items, + onSelect: this._onChange, + prompt: this.props.prompt, + selected: this.state.selectedIndex, + testID: this.props.testID, + style: [styles.pickerWindows, this.props.style], + }; + + return ; + }, + + _onChange: function(event: Object) { + if (this.props.onValueChange) { + var position = event.nativeEvent.position; + if (position >= 0) { + var value = this.props.children[position].props.value; + this.props.onValueChange(value, position); + } else { + this.props.onValueChange(null, position); + } + } + + // The picker is a controlled component. This means we expect the + // on*Change handlers to be in charge of updating our + // `selectedValue` prop. That way they can also + // disallow/undo/mutate the selection of certain values. In other + // words, the embedder of this component should be the source of + // truth, not the native component. + if (this.refs[REF_PICKER] && this.state.selectedIndex !== event.nativeEvent.position) { + this.refs[REF_PICKER].setNativeProps({selected: this.state.selectedIndex}); + } + }, +}); + +var styles = StyleSheet.create({ + pickerWindows: { + // The picker will conform to whatever width is given, but we do + // have to set the component's height explicitly on the + // surrounding view to ensure it gets rendered. + // TODO would be better to export a native constant for this, + // like in iOS the RCTDatePickerManager.m + height: 40, + }, +}); + +var cfg = { + nativeOnly: { + } +}; + +var ComboBoxPicker = requireNativeComponent('RCTPicker', PickerWindows, PickerWindows, cfg); + +module.exports = PickerWindows;