Merge pull request #102 from realm/sk-example-app

Improve ReactExample app
This commit is contained in:
Scott Kyle 2015-10-28 14:03:12 -07:00
commit 5d59040d3d
13 changed files with 681 additions and 288 deletions

View File

@ -0,0 +1,20 @@
{
"env": {
"commonjs": true,
"es6": true
},
"ecmaFeatures": {
"jsx": true
},
"plugins": [
"react"
],
"rules": {
"react/jsx-no-duplicate-props": 2,
"react/jsx-no-undef": 2,
"react/jsx-uses-react": 2,
"react/no-direct-mutation-state": 1,
"react/prefer-es6-class": 1,
"react/react-in-jsx-scope": 2
}
}

View File

@ -133,6 +133,13 @@
remoteGlobalIDString = 02B29A151B7CF7C9008A7E6B; remoteGlobalIDString = 02B29A151B7CF7C9008A7E6B;
remoteInfo = RealmReact; remoteInfo = RealmReact;
}; };
F6EA29001BDEEC4B00ECDC7B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */;
proxyType = 1;
remoteGlobalIDString = 83CBBA2D1A601D0E00E9B192;
remoteInfo = React;
};
/* End PBXContainerItemProxy section */ /* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */ /* Begin PBXCopyFilesBuildPhase section */
@ -342,9 +349,9 @@
027798471BBB2F1000C96559 /* ReactExampleTests */, 027798471BBB2F1000C96559 /* ReactExampleTests */,
83CBBA001A601CBA00E9B192 /* Products */, 83CBBA001A601CBA00E9B192 /* Products */,
); );
indentWidth = 2; indentWidth = 4;
sourceTree = "<group>"; sourceTree = "<group>";
tabWidth = 2; tabWidth = 4;
}; };
83CBBA001A601CBA00E9B192 /* Products */ = { 83CBBA001A601CBA00E9B192 /* Products */ = {
isa = PBXGroup; isa = PBXGroup;
@ -389,6 +396,7 @@
buildRules = ( buildRules = (
); );
dependencies = ( dependencies = (
F6EA29011BDEEC4B00ECDC7B /* PBXTargetDependency */,
F636F6E31BCDB72D0023F35C /* PBXTargetDependency */, F636F6E31BCDB72D0023F35C /* PBXTargetDependency */,
); );
name = ReactExample; name = ReactExample;
@ -403,7 +411,7 @@
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastUpgradeCheck = 0700; LastUpgradeCheck = 0700;
ORGANIZATIONNAME = Facebook; ORGANIZATIONNAME = Realm;
TargetAttributes = { TargetAttributes = {
027798451BBB2F1000C96559 = { 027798451BBB2F1000C96559 = {
CreatedOnToolsVersion = 7.0.1; CreatedOnToolsVersion = 7.0.1;
@ -637,6 +645,11 @@
name = RealmReact; name = RealmReact;
targetProxy = F636F6E21BCDB72D0023F35C /* PBXContainerItemProxy */; targetProxy = F636F6E21BCDB72D0023F35C /* PBXContainerItemProxy */;
}; };
F6EA29011BDEEC4B00ECDC7B /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
name = React;
targetProxy = F6EA29001BDEEC4B00ECDC7B /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */ /* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */ /* Begin PBXVariantGroup section */

View File

@ -1,10 +1,5 @@
/** /* Copyright 2015 Realm Inc - All Rights Reserved
* Copyright (c) 2015-present, Facebook, Inc. * Proprietary and Confidential
* 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.
*/ */
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
@ -66,5 +61,4 @@
//XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); //XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS);
} }
@end @end

View File

@ -0,0 +1,26 @@
/* Copyright 2015 Realm Inc - All Rights Reserved
* Proprietary and Confidential
*/
'use strict';
const Realm = require('realm');
module.exports = new Realm({
schema: [
{
name: 'Todo',
properties: [
{name: 'done', type: Realm.Types.BOOL, default: false},
{name: 'text', type: Realm.Types.STRING, default: ''},
]
},
{
name: 'TodoList',
properties: [
{name: 'name', type: Realm.Types.STRING},
{name: 'items', type: Realm.Types.LIST, objectType: 'Todo'},
]
},
],
});

View File

@ -0,0 +1,79 @@
/* Copyright 2015 Realm Inc - All Rights Reserved
* Proprietary and Confidential
*/
'use strict';
const React = require('react-native');
module.exports = React.StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'stretch',
backgroundColor: '#ffffff',
},
navigator: {
flex: 1,
},
listItem: {
borderColor: "#c8c7cc",
borderBottomWidth: 0.5,
alignItems: 'stretch',
alignSelf: 'stretch',
flexDirection: 'row',
flex: 1,
height: 44,
},
listItemLeftSide: {
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
width: 36,
},
listItemCheckbox: {
borderColor: '#ccc',
borderWidth: 1,
textAlign: 'center',
width: 16,
height: 16,
lineHeight: 14,
},
listItemCount: {
borderColor: '#ccc',
borderWidth: 1,
borderRadius: 8,
textAlign: 'center',
fontSize: 12,
width: 24,
height: 18,
lineHeight: 16,
},
listItemInput: {
fontFamily: 'System',
fontSize: 15,
flexDirection: 'column',
flex: 1,
},
listItemText: {
fontFamily: 'System',
fontSize: 15,
flexDirection: 'column',
flex: 1,
lineHeight: 30,
},
listItemTextSpecial: {
fontStyle: 'italic',
},
listItemDelete: {
paddingLeft: 12,
paddingRight: 12,
flexDirection: 'column',
justifyContent: 'center',
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
}
});

View File

@ -0,0 +1,125 @@
/* Copyright 2015 Realm Inc - All Rights Reserved
* Proprietary and Confidential
*/
'use strict';
const React = require('react-native');
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 {
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 extraItems = [
{name: 'Complete', items: realm.objects('Todo', 'done = true')},
{name: 'Incomplete', items: realm.objects('Todo', 'done = false')},
];
let route = {
title: 'My Todo Lists',
component: TodoListView,
passProps: {
ref: 'listView',
items: this.todoLists,
extraItems: extraItems,
onPressItem: (list) => this._onPressTodoList(list),
},
backButtonTitle: 'Lists',
rightButtonTitle: 'Add',
onRightButtonPress: () => this._addNewTodoList(),
};
return (
<NavigatorIOS ref="nav" initialRoute={route} style={styles.navigator} />
);
}
_addNewTodoItem(list) {
let items = list.items;
if (!this._shouldAddNewItem(items)) {
return;
}
realm.write(() => {
items.push({text: ''});
});
this._setEditingRow(items.length - 1);
}
_addNewTodoList() {
let items = this.todoLists;
if (!this._shouldAddNewItem(items)) {
return;
}
realm.write(() => {
realm.create('TodoList', {name: '', items: []});
});
this._setEditingRow(items.length - 1);
}
_onPressTodoList(list) {
let items = list.items;
let route = {
title: list.name,
component: TodoListView,
passProps: {
ref: 'listItemView',
items: items,
rowClass: TodoItem,
},
};
// Check if the items are mutable (i.e. List rather than Results).
if (items.push) {
Object.assign(route, {
rightButtonTitle: 'Add',
onRightButtonPress: () => this._addNewTodoItem(list),
});
}
this.refs.nav.push(route);
}
_shouldAddNewItem(items) {
let editingRow = this.currentListView.state.editingRow;
let editingItem = editingRow != null && items[editingRow];
// Don't allow adding a new item if the one being edited is empty.
return !editingItem || !!editingItem.text || !!editingItem.name;
}
_setEditingRow(rowIndex) {
// Update the state on the currently displayed TodoList to edit this new item.
this.currentListView.setState({editingRow: rowIndex});
}
}
module.exports = TodoApp;

View File

@ -0,0 +1,58 @@
/* Copyright 2015 Realm Inc - All Rights Reserved
* Proprietary and Confidential
*/
'use strict';
const React = require('react-native');
const TodoListItem = require('./todo-list-item');
const realm = require('./realm');
const styles = require('./styles');
const { Text, TouchableWithoutFeedback, View } = React;
class TodoItem extends TodoListItem {
constructor(props) {
super(props);
this._onPressCheckbox = this._onPressCheckbox.bind(this);
}
get done() {
return this.props.item.done;
}
set done(done) {
this.props.item.done = done;
}
get text() {
return this.props.item.text;
}
set text(text) {
this.props.item.text = text;
}
renderLeftSide() {
return (
<TouchableWithoutFeedback onPress={this._onPressCheckbox}>
<View style={styles.listItemLeftSide}>
<Text style={styles.listItemCheckbox}>
{this.done ? '✓' : ''}
</Text>
</View>
</TouchableWithoutFeedback>
);
}
_onPressCheckbox() {
realm.write(() => {
this.done = !this.done;
});
this.forceUpdate();
}
}
module.exports = TodoItem;

View File

@ -0,0 +1,119 @@
/* Copyright 2015 Realm Inc - All Rights Reserved
* Proprietary and Confidential
*/
'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(extraStyle) {
if (this.props.editing) {
return (
<TextInput
ref="input"
value={this.text}
placeholder="Todo…"
style={[styles.listItemInput, extraStyle]}
onChangeText={this._onChangeText}
onEndEditing={this.props.onEndEditing}
enablesReturnKeyAutomatically={true} />
);
} else {
return (
<Text
style={[styles.listItemText, extraStyle]}
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

@ -0,0 +1,157 @@
/* Copyright 2015 Realm Inc - All Rights Reserved
* Proprietary and Confidential
*/
'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({
sectionHeaderHasChanged: () => false,
rowHasChanged: (row1, row2) => row1 !== row2
});
this.state = {};
this.renderRow = this.renderRow.bind(this);
}
componentDidUpdate() {
let items = this.props.items;
let editingRow = this.state.editingRow;
for (let i = items.length; i--;) {
if (i == editingRow) {
continue;
}
if (this._deleteItemIfEmpty(items[i]) && i < editingRow) {
editingRow--;
}
}
if (editingRow != this.state.editingRow) {
this.setState({editingRow});
}
}
render() {
// Clone the items into a new Array to prevent unexpected errors from changes in length.
let sections = [Array.from(this.props.items)];
let extraItems = this.props.extraItems;
if (extraItems && extraItems.length) {
sections.push(extraItems);
}
let dataSource = this.dataSource.cloneWithRowsAndSections(sections);
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;
let editing = false;
if (sectionIndex == 0) {
RowClass = this.props.rowClass || TodoListItem;
editing = this.state.editingRow == rowIndex;
} else if (sectionIndex == 1) {
RowClass = TodoListExtraItem;
}
return (
<RowClass
item={item}
editing={editing}
onPress={() => this._onPressRow(item, sectionIndex, rowIndex)}
onPressDelete={() => this._onPressDeleteRow(item)}
onEndEditing={() => this._onEndEditingRow(item, rowIndex)} />
);
}
_onPressRow(item, sectionIndex, rowIndex) {
let onPressItem = this.props.onPressItem;
if (onPressItem) {
onPressItem(item);
return;
}
// If no handler was provided, then default to editing the row.
if (sectionIndex == 0) {
this.setState({editingRow: rowIndex});
}
}
_onPressDeleteRow(item) {
this._deleteItem(item);
this.forceUpdate();
}
_onEndEditingRow(item, rowIndex) {
this._deleteItemIfEmpty(item);
if (this.state.editingRow == rowIndex) {
this.setState({editingRow: null});
}
}
_deleteItem(item) {
let items = item.items;
realm.write(() => {
// If the item is a TodoList, then delete all of its items.
if (items && items.length) {
realm.delete(items);
}
realm.delete(item);
});
}
_deleteItemIfEmpty(item) {
// The item could be a TodoList or a Todo.
if (!item.name && !item.text) {
this._deleteItem(item);
return true;
}
return false;
}
}
class TodoListExtraItem extends TodoListItem {
renderText() {
return super.renderText(styles.listItemTextSpecial);
}
renderLeftSide() {
return (
<View style={styles.listItemLeftSide}>
<Text style={styles.listItemCount}>
{this.props.item.items.length}
</Text>
</View>
);
}
renderRightSide() {
return null;
}
}
module.exports = TodoListView;

View File

@ -1,10 +1,5 @@
/** /* Copyright 2015 Realm Inc - All Rights Reserved
* Copyright (c) 2015-present, Facebook, Inc. * Proprietary and Confidential
* 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.
*/ */
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>

View File

@ -1,10 +1,5 @@
/** /* Copyright 2015 Realm Inc - All Rights Reserved
* Copyright (c) 2015-present, Facebook, Inc. * Proprietary and Confidential
* 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.
*/ */
#import "AppDelegate.h" #import "AppDelegate.h"

View File

@ -1,198 +1,10 @@
/** /* Copyright 2015 Realm Inc - All Rights Reserved
* Sample React Native App * Proprietary and Confidential
* https://github.com/facebook/react-native
*/ */
'use strict'; 'use strict';
var Realm = require('realm'); const React = require('react-native');
var React = require('react-native'); const TodoApp = require('./components/todo-app');
var { React.AppRegistry.registerComponent('ReactExample', () => TodoApp);
AppRegistry,
StyleSheet,
NavigatorIOS,
AlertIOS,
ListView,
TouchableHighlight,
Text,
TextInput,
View,
} = React;
var TodoItemSchema = {
name: 'Todo',
properties: [
{name: 'text', type: Realm.Types.STRING},
]
};
var TodoListSchmea = {
name: 'TodoList',
properties: [
{name: 'name', type: Realm.Types.STRING},
{name: 'items', type: Realm.Types.LIST, objectType: 'Todo'}
]
};
console.log(Realm.defaultPath);
var realm = new Realm({schema: [TodoItemSchema, TodoListSchmea]});
class Edit extends React.Component {
componentWillMount() {
this.setState({text: this.props.text});
}
save() {
realm.write(function () {
if (this.props.todoId == this.props.list.items.length) {
this.props.list.items.push({text: this.state.text});
}
else {
var todoItem = this.props.list.items[this.props.todoId];
todoItem.text = this.state.text;
}
}.bind(this));
// should not be needed once we have notifications
this.props.parent.updateDataSource();
this.props.navigator.pop();
}
render() {
return (
<View style={{flex:1, justifyContent: 'flex-start'}}>
<TextInput multiline={true} style={styles.textInput}
placeholder='Enter Todo' autoFocus={true}
onChangeText={(text) => this.setState({text})} value={this.state.text}/>
<TouchableHighlight
style={styles.button}
onPress={this.save.bind(this)}
underlayColor='#99d9f4'>
<Text style={styles.buttonText}>Save</Text>
</TouchableHighlight>
</View>
)
}
};
class TodoList extends React.Component {
componentWillMount() {
this.lists = realm.objects('TodoList');
if (this.lists.length < 1) {
realm.write(function() {
realm.create('TodoList', ['List', []]);
});
}
this.list = this.lists[0];
this.menu = this.menu.bind(this);
this.delete = this.delete.bind(this);
var dataSource = new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2
});
this.updateDataSource(dataSource);
}
updateDataSource(oldDataSource) {
if (!oldDataSource) {
oldDataSource = this.state.dataSource;
}
this.setState({dataSource: oldDataSource.cloneWithRows(this.list.items)});
}
menu(todo, todoID) {
AlertIOS.alert(
todo.text,
todoID,
[
{text: 'Complete', onPress: () => this.delete(todoID)},
{text: 'Edit', onPress: () => this.edit(todoID, todo.text)},
{text: 'Cancel'}
]
)
}
delete(todoID) {
var item = this.list.items[todoID];
realm.write(function() {
realm.delete(item);
})
this.updateDataSource();
}
edit(todoId, text) {
this.props.navigator.push({
title: text,
component: Edit,
passProps: {list: this.list, todoId: todoId, text: text, parent: this}
});
}
render() {
return (
<View style={styles.container}>
<ListView style={styles.listView} dataSource={this.state.dataSource} renderRow={(rowData, sectionID, rowID) =>
<TouchableHighlight style={styles.listItem} onPress={() => this.menu(rowData, rowID)}>
<Text>{rowData.text}</Text>
</TouchableHighlight>
}/>
<TouchableHighlight style={styles.button}
onPress={() => this.edit(this.list.items.length, "")}>
<Text style={styles.buttonText}>+</Text>
</TouchableHighlight>
<Text style={styles.instructions}>
Press Cmd+R to reload,{'\n'}
Cmd+Control+Z for dev menu
</Text>
</View>
);
}
};
class Navigator extends React.Component {
render() {
return (
<NavigatorIOS initialRoute={{component: TodoList, title: 'Todo Items'}} style={{flex:1}}/>
);
}
};
AppRegistry.registerComponent('ReactExample', () => Navigator);
var styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'stretch',
backgroundColor: '#ffffff',
},
listItem: {
marginTop: 3,
padding: 6,
backgroundColor:'#ACACAC',
alignSelf: 'stretch',
flexDirection: 'row',
flex:1,
},
textInput: {
alignSelf: 'stretch',
borderWidth: 0.5,
borderColor: '#0f0f0f',
height: 200,
fontSize: 13,
margin: 6,
marginTop: 70,
padding: 4,
},
button: {
height: 36,
backgroundColor: '#48BBEC',
alignSelf: 'stretch',
justifyContent: 'center'
},
buttonText: {
alignSelf: 'center',
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
}
});