Example app can now have multiple todo lists

This commit is contained in:
Scott Kyle 2015-10-27 01:52:26 -07:00
parent e90715140b
commit 6fc2110991
8 changed files with 289 additions and 204 deletions

View File

@ -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,
},

View File

@ -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 (
<NavigatorIOS ref="nav" initialRoute={route} style={{flex: 1}} />
<NavigatorIOS ref="nav" initialRoute={route} style={styles.navigator} />
);
}
_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});
}
}

View File

@ -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 (
<TouchableWithoutFeedback onPress={this.props.onPress}>
<View style={styles.listItemCheckboxContainer}>
<View style={styles.listItemCheckbox}>
<Text>{this.props.checked ? '✓' : ''}</Text>
</View>
</View>
</TouchableWithoutFeedback>
);
}
}
module.exports = TodoItemCheckbox;

View File

@ -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 (
<TouchableWithoutFeedback onPress={this.props.onPress}>
<View style={styles.listItemDelete}>
<Text>𐄂</Text>
</View>
</TouchableWithoutFeedback>
);
}
}
module.exports = TodoItemDelete;

View File

@ -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 = (
<TextInput
ref="input"
value={item.text}
placeholder="Call Mom"
style={styles.listItemInput}
onChangeText={this._onChangeText}
onEndEditing={this.props.onEndEditing}
enablesReturnKeyAutomatically={true} />
);
} else {
contents = (
<Text
style={styles.listItemText}
onPress={this.props.onPress}
suppressHighlighting={true}>
{item.text}
</Text>
);
deleteButton = (
<TodoItemDelete onPress={this.props.onPressDelete} />
);
}
set text(text) {
this.props.item.text = text;
}
renderLeftSide() {
return (
<View style={styles.listItem}>
<TodoItemCheckbox checked={item.done} onPress={this._onPressCheckbox} />
{contents}
{deleteButton}
</View>
<TouchableWithoutFeedback onPress={this._onPressCheckbox}>
<View style={styles.listItemLeftSide}>
<View style={styles.listItemCheckbox}>
<Text>{this.done ? '✓' : ''}</Text>
</View>
</View>
</TouchableWithoutFeedback>
);
}
_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;

View File

@ -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 (
<View style={styles.listItem}>
{this.renderLeftSide()}
{this.renderText()}
{this.renderRightSide()}
</View>
);
}
renderLeftSide() {
return (
<View style={styles.listItemLeftSide}>
<Text>{this.done ? '✓' : ''}</Text>
</View>
);
}
renderRightSide() {
// Only show the delete button while not editing the text.
return this.props.editing ? null : this.renderDelete();
}
renderText() {
if (this.props.editing) {
return (
<TextInput
ref="input"
value={this.text}
placeholder="Call Mom"
style={styles.listItemInput}
onChangeText={this._onChangeText}
onEndEditing={this.props.onEndEditing}
enablesReturnKeyAutomatically={true} />
);
} else {
return (
<Text
style={styles.listItemText}
onPress={this.props.onPress}
suppressHighlighting={true}>
{this.text}
</Text>
);
}
}
renderDelete() {
return (
<TouchableWithoutFeedback onPress={this.props.onPressDelete}>
<View style={styles.listItemDelete}>
<Text>𐄂</Text>
</View>
</TouchableWithoutFeedback>
);
}
_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;

View File

@ -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 (
<View style={styles.container}>
<ListView style={styles.listView} dataSource={dataSource} renderRow={this._renderRow} />
<Text style={styles.instructions}>
Press Cmd+R to reload,{'\n'}
Cmd+D for dev menu
</Text>
</View>
);
}
_renderRow(item, sectionIndex, rowIndex) {
return (
<TodoItem
item={item}
editing={this.state.editingRow == rowIndex}
onPress={() => 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;

View File

@ -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 (
<View style={styles.container}>
<ListView style={styles.listView} dataSource={dataSource} renderRow={this.renderRow} />
<Text style={styles.instructions}>
Press Cmd+R to reload,{'\n'}
Cmd+D for dev menu
</Text>
</View>
);
}
renderRow(item, sectionIndex, rowIndex) {
let RowClass = this.props.rowClass || TodoListItem;
return (
<RowClass
item={item}
editing={this.state.editingRow == rowIndex}
onPress={() => 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;