diff --git a/examples/ReactExample/components/styles.js b/examples/ReactExample/components/styles.js index 47bab8a4..d31ea264 100644 --- a/examples/ReactExample/components/styles.js +++ b/examples/ReactExample/components/styles.js @@ -9,6 +9,9 @@ module.exports = React.StyleSheet.create({ alignItems: 'stretch', backgroundColor: '#ffffff', }, + navigator: { + flex: 1, + }, listItem: { borderColor: "#c8c7cc", borderBottomWidth: 0.5, @@ -18,16 +21,15 @@ module.exports = React.StyleSheet.create({ flex: 1, height: 44, }, - listItemCheckboxContainer: { - paddingLeft: 12, - paddingRight: 4, + listItemLeftSide: { flexDirection: 'column', + alignItems: 'center', justifyContent: 'center', + width: 36, }, listItemCheckbox: { borderColor: "#000", borderWidth: 0.5, - marginRight: 8, width: 16, height: 16, }, diff --git a/examples/ReactExample/components/todo-app.js b/examples/ReactExample/components/todo-app.js index afebfc09..e817d8c0 100644 --- a/examples/ReactExample/components/todo-app.js +++ b/examples/ReactExample/components/todo-app.js @@ -1,50 +1,85 @@ 'use strict'; const React = require('react-native'); -const TodoList = require('./todo-list'); +const TodoItem = require('./todo-item'); +const TodoListView = require('./todo-listview'); const realm = require('./realm'); +const styles = require('./styles'); const { NavigatorIOS } = React; class TodoApp extends React.Component { - componentWillMount() { - let todoLists = realm.objects('TodoList'); + constructor(props) { + super(props); + let todoLists = realm.objects('TodoList'); if (todoLists.length < 1) { realm.write(() => { realm.create('TodoList', {name: 'Todo List', items: []}); }); } + // This is a Results object, which will live-update. this.todoLists = todoLists; + this.state = {}; + } + + get currentListView() { + let refs = this.refs.nav.refs; + return refs.listItemView || refs.listView; } render() { - let list = this.todoLists[0]; - let route = { - title: list.name, - component: TodoList, + title: 'My Todo Lists', + component: TodoListView, passProps: { - ref: 'todoList', - list: list, + ref: 'listView', + items: this.todoLists, + onPressItem: (list) => this._onPressTodoList(list), }, rightButtonTitle: 'Add', - onRightButtonPress: () => this._addNewItem(list) + onRightButtonPress: () => this._addNewTodoList(), }; return ( - + ); } - _addNewItem(list) { + _addNewTodoItem(list) { realm.write(() => { list.items.push({text: ''}); }); - let todoList = this.refs.nav.refs.todoList; - todoList.setState({editingRow: list.items.length - 1}); + this._setEditingRow(list.items.length - 1); + } + + _addNewTodoList() { + realm.write(() => { + realm.create('TodoList', {name: '', items: []}); + }); + + this._setEditingRow(this.todoLists.length - 1); + } + + _onPressTodoList(list) { + this.refs.nav.push({ + title: list.name, + component: TodoListView, + passProps: { + ref: 'listItemView', + items: list.items, + rowClass: TodoItem, + }, + rightButtonTitle: 'Add', + onRightButtonPress: () => this._addNewTodoItem(list), + }); + } + + _setEditingRow(rowIndex) { + // Update the state on the currently displayed TodoList to edit this new item. + this.currentListView.setState({editingRow: rowIndex}); } } diff --git a/examples/ReactExample/components/todo-item-checkbox.js b/examples/ReactExample/components/todo-item-checkbox.js deleted file mode 100644 index 6cfbd3cf..00000000 --- a/examples/ReactExample/components/todo-item-checkbox.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -const React = require('react-native'); -const styles = require('./styles'); - -const { Text, TouchableWithoutFeedback, View } = React; - -class TodoItemCheckbox extends React.Component { - render() { - return ( - - - - {this.props.checked ? '✓' : ''} - - - - ); - } -} - -module.exports = TodoItemCheckbox; diff --git a/examples/ReactExample/components/todo-item-delete.js b/examples/ReactExample/components/todo-item-delete.js deleted file mode 100644 index 4a260d4b..00000000 --- a/examples/ReactExample/components/todo-item-delete.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; - -const React = require('react-native'); -const styles = require('./styles'); - -const { Text, TouchableWithoutFeedback, View } = React; - -class TodoItemDelete extends React.Component { - render() { - return ( - - - 𐄂 - - - ); - } -} - -module.exports = TodoItemDelete; diff --git a/examples/ReactExample/components/todo-item.js b/examples/ReactExample/components/todo-item.js index 988b7240..d02667b5 100644 --- a/examples/ReactExample/components/todo-item.js +++ b/examples/ReactExample/components/todo-item.js @@ -1,97 +1,54 @@ 'use strict'; const React = require('react-native'); -const TodoItemCheckbox = require('./todo-item-checkbox'); -const TodoItemDelete = require('./todo-item-delete'); +const TodoListItem = require('./todo-list-item'); const realm = require('./realm'); const styles = require('./styles'); -const { Text, TextInput, View } = React; +const { Text, TouchableWithoutFeedback, View } = React; -class TodoItem extends React.Component { +class TodoItem extends TodoListItem { constructor(props) { super(props); - this._onChangeText = this._onChangeText.bind(this); this._onPressCheckbox = this._onPressCheckbox.bind(this); } - componentDidMount() { - // The autoFocus prop on TextInput was not working for us :( - this._focusInputIfNecessary(); + get done() { + return this.props.item.done; } - componentDidUpdate() { - this._focusInputIfNecessary(); + set done(done) { + this.props.item.done = done; } - render() { - let item = this.props.item; - let deleteButton; - let contents; + get text() { + return this.props.item.text; + } - if (this.props.editing) { - contents = ( - - ); - } else { - contents = ( - - {item.text} - - ); - - deleteButton = ( - - ); - } + set text(text) { + this.props.item.text = text; + } + renderLeftSide() { return ( - - - {contents} - {deleteButton} - + + + + {this.done ? '✓' : ''} + + + ); } - _onChangeText(text) { - realm.write(() => { - this.props.item.text = text; - }); - - this.forceUpdate(); - } - _onPressCheckbox() { - let item = this.props.item; realm.write(() => { - item.done = !item.done; + this.done = !this.done; }); this.forceUpdate(); } - - _focusInputIfNecessary() { - if (!this.props.editing) { - return; - } - - let input = this.refs.input; - if (!input.isFocused()) { - input.focus(); - } - } } module.exports = TodoItem; diff --git a/examples/ReactExample/components/todo-list-item.js b/examples/ReactExample/components/todo-list-item.js new file mode 100644 index 00000000..c8a39b32 --- /dev/null +++ b/examples/ReactExample/components/todo-list-item.js @@ -0,0 +1,115 @@ +'use strict'; + +const React = require('react-native'); +const realm = require('./realm'); +const styles = require('./styles'); + +const { Text, TextInput, TouchableWithoutFeedback, View } = React; + +class TodoListItem extends React.Component { + constructor(props) { + super(props); + + this._onChangeText = this._onChangeText.bind(this); + } + + get done() { + let items = this.props.item.items; + return items.length > 0 && Array.prototype.every.call(items, (item) => item.done); + } + + get text() { + return this.props.item.name; + } + + set text(text) { + this.props.item.name = text; + } + + componentDidMount() { + // The autoFocus prop on TextInput was not working for us :( + this._focusInputIfNecessary(); + } + + componentDidUpdate() { + this._focusInputIfNecessary(); + } + + render() { + return ( + + {this.renderLeftSide()} + {this.renderText()} + {this.renderRightSide()} + + ); + } + + renderLeftSide() { + return ( + + {this.done ? '✓' : '⁃'} + + ); + } + + renderRightSide() { + // Only show the delete button while not editing the text. + return this.props.editing ? null : this.renderDelete(); + } + + renderText() { + if (this.props.editing) { + return ( + + ); + } else { + return ( + + {this.text} + + ); + } + } + + renderDelete() { + return ( + + + 𐄂 + + + ); + } + + _onChangeText(text) { + realm.write(() => { + this.text = text; + }); + + this.forceUpdate(); + } + + _focusInputIfNecessary() { + if (!this.props.editing) { + return; + } + + let input = this.refs.input; + if (!input.isFocused()) { + input.focus(); + } + } +} + +module.exports = TodoListItem; diff --git a/examples/ReactExample/components/todo-list.js b/examples/ReactExample/components/todo-list.js deleted file mode 100644 index af06dacc..00000000 --- a/examples/ReactExample/components/todo-list.js +++ /dev/null @@ -1,79 +0,0 @@ -'use strict'; - -const React = require('react-native'); -const TodoItem = require('./todo-item'); -const realm = require('./realm'); -const styles = require('./styles'); - -const { ListView, Text, View } = React; - -class TodoList extends React.Component { - constructor(props) { - super(props); - - this.dataSource = new ListView.DataSource({ - rowHasChanged: (row1, row2) => row1 !== row2 - }); - - this.state = {}; - this._renderRow = this._renderRow.bind(this); - } - - render() { - let dataSource = this.dataSource.cloneWithRows(this.props.list.items); - - return ( - - - - Press Cmd+R to reload,{'\n'} - Cmd+D for dev menu - - - ); - } - - _renderRow(item, sectionIndex, rowIndex) { - return ( - this._onPressRow(rowIndex)} - onPressDelete={() => this._onPressDeleteRow(rowIndex)} - onEndEditing={() => this._onEndEditingRow(rowIndex)} /> - ); - } - - _onPressRow(rowIndex) { - let editingRow = this.state.editingRow; - - if (editingRow != null && editingRow != rowIndex) { - this._onEndEditingRow(editingRow); - } - - this.setState({editingRow: rowIndex}); - } - - _onPressDeleteRow(rowIndex) { - let items = this.props.list.items; - - realm.write(() => items.splice(rowIndex, 1)); - - this.forceUpdate(); - } - - _onEndEditingRow(rowIndex) { - let items = this.props.list.items; - - // Delete the todo item if it doesn't have any text. - if (!items[rowIndex].text) { - realm.write(() => items.splice(rowIndex, 1)); - } - - if (this.state.editingRow == rowIndex) { - this.setState({editingRow: null}); - } - } -} - -module.exports = TodoList; diff --git a/examples/ReactExample/components/todo-listview.js b/examples/ReactExample/components/todo-listview.js new file mode 100644 index 00000000..00eab958 --- /dev/null +++ b/examples/ReactExample/components/todo-listview.js @@ -0,0 +1,97 @@ +'use strict'; + +const React = require('react-native'); +const TodoListItem = require('./todo-list-item'); +const realm = require('./realm'); +const styles = require('./styles'); + +const { ListView, Text, View } = React; + +class TodoListView extends React.Component { + constructor(props) { + super(props); + + this.dataSource = new ListView.DataSource({ + rowHasChanged: (row1, row2) => row1 !== row2 + }); + + this.state = {}; + this.renderRow = this.renderRow.bind(this); + } + + componentWillUpdate(nextProps, nextState) { + let editingRow = this.state.editingRow; + + if (editingRow != null && editingRow != nextState.editingRow) { + let item = this.props.items[editingRow]; + + // The item may have already been deleted. + if (item) { + this._deleteItemIfEmpty(item); + } + } + } + + render() { + // Clone the items into a new Array to prevent unexpected errors from changes in length. + let items = Array.from(this.props.items); + let dataSource = this.dataSource.cloneWithRows(items); + + return ( + + + + Press Cmd+R to reload,{'\n'} + Cmd+D for dev menu + + + ); + } + + renderRow(item, sectionIndex, rowIndex) { + let RowClass = this.props.rowClass || TodoListItem; + + return ( + this._onPressRow(item, rowIndex)} + onPressDelete={() => this._onPressDeleteRow(item, rowIndex)} + onEndEditing={() => this._onEndEditingRow(item, rowIndex)} /> + ); + } + + _onPressRow(item, rowIndex) { + let onPressItem = this.props.onPressItem; + if (onPressItem) { + onPressItem(item, rowIndex); + return; + } + + // If no handler was provided, then default to editing the row. + this.setState({editingRow: rowIndex}); + } + + _onPressDeleteRow(item) { + realm.write(() => realm.delete(item)); + + this.forceUpdate(); + } + + _onEndEditingRow(item, rowIndex) { + this._deleteItemIfEmpty(item); + + if (this.state.editingRow == rowIndex) { + this.setState({editingRow: null}); + } + } + + _deleteItemIfEmpty(item) { + // The item could be a TodoList or a Todo. + if (!item.name && !item.text) { + realm.write(() => realm.delete(item)); + } + } +} + +module.exports = TodoListView;