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', alignItems: 'stretch',
backgroundColor: '#ffffff', backgroundColor: '#ffffff',
}, },
navigator: {
flex: 1,
},
listItem: { listItem: {
borderColor: "#c8c7cc", borderColor: "#c8c7cc",
borderBottomWidth: 0.5, borderBottomWidth: 0.5,
@ -18,16 +21,15 @@ module.exports = React.StyleSheet.create({
flex: 1, flex: 1,
height: 44, height: 44,
}, },
listItemCheckboxContainer: { listItemLeftSide: {
paddingLeft: 12,
paddingRight: 4,
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
width: 36,
}, },
listItemCheckbox: { listItemCheckbox: {
borderColor: "#000", borderColor: "#000",
borderWidth: 0.5, borderWidth: 0.5,
marginRight: 8,
width: 16, width: 16,
height: 16, height: 16,
}, },

View File

@ -1,50 +1,85 @@
'use strict'; 'use strict';
const React = require('react-native'); 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 realm = require('./realm');
const styles = require('./styles');
const { NavigatorIOS } = React; const { NavigatorIOS } = React;
class TodoApp extends React.Component { class TodoApp extends React.Component {
componentWillMount() { constructor(props) {
let todoLists = realm.objects('TodoList'); super(props);
let todoLists = realm.objects('TodoList');
if (todoLists.length < 1) { if (todoLists.length < 1) {
realm.write(() => { realm.write(() => {
realm.create('TodoList', {name: 'Todo List', items: []}); realm.create('TodoList', {name: 'Todo List', items: []});
}); });
} }
// This is a Results object, which will live-update.
this.todoLists = todoLists; this.todoLists = todoLists;
this.state = {};
}
get currentListView() {
let refs = this.refs.nav.refs;
return refs.listItemView || refs.listView;
} }
render() { render() {
let list = this.todoLists[0];
let route = { let route = {
title: list.name, title: 'My Todo Lists',
component: TodoList, component: TodoListView,
passProps: { passProps: {
ref: 'todoList', ref: 'listView',
list: list, items: this.todoLists,
onPressItem: (list) => this._onPressTodoList(list),
}, },
rightButtonTitle: 'Add', rightButtonTitle: 'Add',
onRightButtonPress: () => this._addNewItem(list) onRightButtonPress: () => this._addNewTodoList(),
}; };
return ( return (
<NavigatorIOS ref="nav" initialRoute={route} style={{flex: 1}} /> <NavigatorIOS ref="nav" initialRoute={route} style={styles.navigator} />
); );
} }
_addNewItem(list) { _addNewTodoItem(list) {
realm.write(() => { realm.write(() => {
list.items.push({text: ''}); list.items.push({text: ''});
}); });
let todoList = this.refs.nav.refs.todoList; this._setEditingRow(list.items.length - 1);
todoList.setState({editingRow: 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'; 'use strict';
const React = require('react-native'); const React = require('react-native');
const TodoItemCheckbox = require('./todo-item-checkbox'); const TodoListItem = require('./todo-list-item');
const TodoItemDelete = require('./todo-item-delete');
const realm = require('./realm'); const realm = require('./realm');
const styles = require('./styles'); 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) { constructor(props) {
super(props); super(props);
this._onChangeText = this._onChangeText.bind(this);
this._onPressCheckbox = this._onPressCheckbox.bind(this); this._onPressCheckbox = this._onPressCheckbox.bind(this);
} }
componentDidMount() { get done() {
// The autoFocus prop on TextInput was not working for us :( return this.props.item.done;
this._focusInputIfNecessary();
} }
componentDidUpdate() { set done(done) {
this._focusInputIfNecessary(); this.props.item.done = done;
} }
render() { get text() {
let item = this.props.item; return this.props.item.text;
let deleteButton; }
let contents;
if (this.props.editing) { set text(text) {
contents = ( this.props.item.text = text;
<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} />
);
}
renderLeftSide() {
return ( return (
<View style={styles.listItem}> <TouchableWithoutFeedback onPress={this._onPressCheckbox}>
<TodoItemCheckbox checked={item.done} onPress={this._onPressCheckbox} /> <View style={styles.listItemLeftSide}>
{contents} <View style={styles.listItemCheckbox}>
{deleteButton} <Text>{this.done ? '✓' : ''}</Text>
</View> </View>
</View>
</TouchableWithoutFeedback>
); );
} }
_onChangeText(text) {
realm.write(() => {
this.props.item.text = text;
});
this.forceUpdate();
}
_onPressCheckbox() { _onPressCheckbox() {
let item = this.props.item;
realm.write(() => { realm.write(() => {
item.done = !item.done; this.done = !this.done;
}); });
this.forceUpdate(); this.forceUpdate();
} }
_focusInputIfNecessary() {
if (!this.props.editing) {
return;
}
let input = this.refs.input;
if (!input.isFocused()) {
input.focus();
}
}
} }
module.exports = TodoItem; 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;