Created
March 15, 2017 18:30
-
-
Save whoyawn/930a6c8a02874ded1adc9669cce65b72 to your computer and use it in GitHub Desktop.
practice with nested listviews
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Created by huyanh on 2017. 3. 14.. | |
*/ | |
import React, { Component } from 'react'; | |
import { | |
AsyncStorage, | |
ListView, | |
Text, | |
View, | |
} from 'react-native'; | |
import ViewPager from 'react-native-viewpager'; | |
import moment from 'moment'; | |
import Page from './page'; | |
type Props = {} | |
type PageEntry = { | |
// key: string, | |
pageID: number | string, | |
date: string, | |
total: number, | |
// inputTitle: string, | |
// inputAmount: number, | |
items: Object[], | |
} | |
sample = [ | |
{loading: true, total: 50, items: []}, | |
] | |
class App extends Component { | |
state: { | |
pages: PageEntry[], | |
}; | |
constructor(props: Props) { | |
super(props); | |
const ds = new ViewPager.DataSource({ | |
pageHasChanged: (p1, p2) => p1 !== p2, | |
}); | |
this.state = { | |
pages: sample, | |
dataSource: ds.cloneWithPages(sample), | |
}; | |
this.handleUpdate = this.handleUpdate.bind(this); | |
this.renderPage = this.renderPage.bind(this); | |
} | |
// Every new day add a page | |
addPage() { | |
const newPages = [ | |
...this.state.pages, | |
{ | |
key: Date.now(), | |
date: moment(), | |
total: 0, | |
items: [], | |
}, | |
]; | |
this.setState({ | |
pages: newPages, | |
}); | |
} | |
setSource(pages, pagesDataSource, otherState: any = {}) { | |
// for top level pages | |
this.setState({ | |
pages, | |
// allows us to keep track of different items than are rendered on the screen | |
dataSource: this.state.dataSource.cloneWithPages(pagesDataSource), | |
...otherState, // spread in any other state given | |
}); | |
} | |
handleUpdate(pageID: number | string, updatedPage: PageEntry) { | |
const newPages = this.state.pages.map((page) => { | |
if (page.pageID !== pageID) return page; | |
return { | |
updatedPage, | |
}; | |
}); | |
// console.log('new pages', newPages); | |
// console.log(pageID); | |
this.setSource(newPages, newPages); | |
} | |
renderPage(data: PageEntry, pageID: number | string) { | |
// nested list | |
const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 }); | |
// page object | |
const dataWithDS = { | |
...data, | |
source: ds.cloneWithRows(data.items), | |
}; | |
// TODO: refactor this and above to one function | |
// const newPages = this.state.pages.map((page) => { | |
// if (page.pageID !== pageID) return page; | |
// return { | |
// dataWithDS, | |
// }; | |
// }); | |
// this.setSource(newPages, newPages); | |
console.log('hi', pageID); | |
console.log('pages',this.state.pages); | |
console.log(this.state.pages[pageID]); | |
console.log(dataWithDS); | |
return ( | |
<Page | |
date="hi" | |
data={dataWithDS} | |
onUpdate={page => this.handleUpdate(pageID, page)} | |
/> | |
); | |
} | |
render() { | |
return ( | |
<ViewPager | |
dataSource={this.state.dataSource} | |
renderPage={this.renderPage} | |
isLoop={false} | |
autoPlay={false} | |
/> | |
); | |
} | |
} | |
export default App; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Created by huyanh on 2017. 2. 8.. | |
*/ | |
// @flow | |
import React, { Component } from 'react'; | |
import { View, StyleSheet, ActivityIndicator, Platform, ListView, Keyboard, AsyncStorage } from 'react-native'; | |
import Header from './header'; | |
import FoodInput from './foodinput'; | |
import Row from './row'; | |
type Props = { | |
date: string, | |
data: Object, | |
onUpdate: (Object) => void, | |
// total: number, | |
// inputTitle: string, | |
// inputAmount: number, | |
// items: FoodEntry[], | |
} | |
type FoodEntry = {key: string, title: string, amount: string, editing: boolean} | |
class Page extends Component { | |
state: { | |
loading: boolean, | |
total: number, | |
title: string, | |
amount: string, | |
// items: FoodEntry[], | |
// dataSource: any, | |
}; | |
constructor(props: Props) { | |
super(props); | |
// A rowHasChanged function is required to use ListView. Here we just say a | |
// row has changed if the row we are on is not the same as the previous row. | |
// const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 }); | |
this.state = { | |
loading: true, | |
total: 0, | |
title: '', // will hold value for textinput | |
amount: '', | |
// items: [], // food items to input | |
// The clone methods suck in the new data and compute a diff for each row | |
// so ListView knows whether to re-render it or not. | |
// dataSource: ds.cloneWithRows([]), | |
}; | |
this.handleUpdateText = this.handleUpdateText.bind(this); | |
this.handleToggleEditing = this.handleToggleEditing.bind(this); | |
this.handleRemoveItem = this.handleRemoveItem.bind(this); | |
this.setSource = this.setSource.bind(this); | |
this.handleAddItem = this.handleAddItem.bind(this); | |
} | |
componentWillMount() { | |
} | |
componentDidMount() { | |
this.props.onUpdate({ loading: false, items: [], total: 30 }); | |
// AsyncStorage.multiGet(['items', 'total']).then((store) => { | |
// try { | |
// const items = JSON.parse(store[0][1]); | |
// const total = items.length > 0 ? parseInt(store[1][1], 10) : 0; | |
// // this.setSource(items, items, total, { loading: false }); | |
// | |
// } catch (e) { | |
// console.log('error'); | |
// } | |
// }); | |
} | |
handleUpdateText(key: string, text: string) { | |
const newItems = this.state.items.map((item) => { | |
if (item.key !== key) return item; | |
return { | |
...item, | |
text, | |
}; | |
}); | |
this.setSource(newItems, newItems); | |
} | |
handleToggleEditing(key: string, editing: boolean) { | |
const newItems = this.state.items.map((item) => { | |
if (item.key !== key) return item; | |
return { | |
...item, | |
editing, | |
}; | |
}); | |
this.setSource(newItems, newItems); | |
} | |
setSource(items, itemsDataSource, | |
total: number = this.state.total, | |
otherState: any = {}) { | |
// this.setState({ | |
// items, | |
// // allows us to keep track of different items than are rendered on the screen | |
// dataSource: this.state.dataSource.cloneWithRows(itemsDataSource), | |
// total, | |
// ...otherState, // spread in any other state given | |
// }); | |
console.log('save', total); | |
const strTotal = total > 0 ? total.toString() : '0'; | |
this.props.onUpdate({ items, total: strTotal }); | |
// AsyncStorage.multiSet([['items', JSON.stringify(items)], ['total', strTotal]]); | |
} | |
handleRemoveItem(key: string) { | |
// TODO: subtract from total | |
// const newTotal: number = this.state.total - amount < 0 ? 0 : this.state.total - amount; | |
// this.setState({ total: newTotal }); | |
let total = this.state.total; | |
const newItems = this.state.items.filter((item) => { | |
if (item.key === key) { | |
total = this.state.total - item.amount; | |
} | |
return item.key !== key; | |
}); | |
this.setSource(newItems, newItems, total); | |
} | |
handleAddItem() { | |
// if i gave handleadditem a parameter bool then it does weird behavior if | |
// i set the handler for FoodInput passing in a bool | |
if (!this.state.amount) return; // don't add empty calories | |
const newItems = [ | |
...this.props.data.items, // spread old items into array | |
{ | |
key: Date.now(), // just unique identifier | |
title: this.state.title, // add text of value just entered | |
amount: this.state.amount, | |
}, | |
]; | |
if (this.state.amount !== '') { | |
// yell | |
} | |
const total = this.state.total + parseInt(this.state.amount, 10); | |
// now set new state and clear out text | |
this.props.onUpdate({ items: newItems, total }); | |
//this.setSource(newItems, newItems, total, { title: '', amount: '' }); | |
} | |
render() { | |
return ( | |
<View style={styles.container}> | |
<Header | |
style={styles.header} | |
total={this.props.data.total} | |
/> | |
<FoodInput | |
title={this.state.title} | |
amount={this.state.amount} | |
onAddAmount={this.handleAddItem} | |
onChangeTitle={title => this.setState({ title })} | |
onChangeAmount={amount => this.setState({ amount })} | |
/> | |
<View style={styles.content}> | |
<ListView | |
style={styles.content} | |
enableEmptySections | |
dataSource={this.props.data.source} | |
onScroll={() => Keyboard.dismiss()} | |
// passed value of what we set in our datasource | |
// each value of the spread is the rest of the object | |
renderRow={({ key, ...value }) => ( | |
<Row | |
key={key} | |
onUpdate={text => this.handleUpdateText(key, text)} | |
onToggleEdit={(editing) => { | |
console.log(editing.type); | |
this.handleToggleEditing(key, editing); | |
}} | |
onRemove={() => this.handleRemoveItem(key)} | |
{...value} | |
/> | |
)} | |
renderSeparator={(sectionId, rowId) => <View key={rowId} style={styles.separator} />} | |
/> | |
</View> | |
{this.props.loading && <View style={styles.loading}> | |
<ActivityIndicator | |
animating | |
size="large" | |
/> | |
</View>} | |
</View> | |
); | |
} | |
} | |
const styles = StyleSheet.create({ | |
container: { | |
flex: 1, | |
backgroundColor: '#F5F5F5', | |
...Platform.select({ | |
ios: { paddingTop: 30 }, | |
}), | |
}, | |
header: { | |
flex: 5, | |
}, | |
loading: { | |
position: 'absolute', | |
left: 0, | |
top: 0, | |
right: 0, | |
bottom: 0, | |
alignItems: 'center', | |
justifyContent: 'center', | |
backgroundColor: 'rgba(0,0,0,.2)', | |
}, | |
content: { | |
flex: 2, | |
}, | |
list: { | |
backgroundColor: '#FFF', | |
borderColor: '#F5F5F5', | |
}, | |
separator: { | |
borderWidth: 1, | |
borderColor: '#F5F5F5', | |
}, | |
}); | |
export default Page; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Created by huyanh on 2017. 2. 9.. | |
*/ | |
import React, {Component} from 'react'; | |
import {View, Text, StyleSheet, TouchableOpacity, TextInput} from 'react-native'; | |
type Props = { | |
onToggleEdit: (boolean) => void, | |
title: string, | |
onUpdate: () => void, | |
amount: number, | |
onRemove: () => void, | |
} | |
class Row extends Component { | |
props: Props; | |
render() { // this.props.text comes from spread operator in renderRow | |
const textTitleComponent = ( | |
<TouchableOpacity onLongPress={() => this.props.onToggleEdit(true)}> | |
<Text style={styles.text}>{this.props.title}</Text> | |
</TouchableOpacity> | |
); | |
const editingTitleComponent = ( | |
<View style={styles.textWrap}> | |
<TextInput | |
onChangeText={this.props.onUpdate} | |
autoFocus | |
value={this.props.title} | |
style={styles.input} | |
multiline | |
/> | |
</View> | |
); | |
const textAmountComponent = ( | |
<TouchableOpacity onLongPress={() => this.props.onToggleEdit(true)}> | |
<Text style={styles.text}>{this.props.amount}</Text> | |
</TouchableOpacity> | |
); | |
const editingAmountComponent = ( | |
<View style={styles.textWrap}> | |
<TextInput | |
onChangeText={this.props.onUpdate} | |
autoFocus | |
value={this.props.amount} | |
style={styles.input} | |
multiline | |
/> | |
</View> | |
); | |
const doneButton = ( | |
<TouchableOpacity style={styles.done} onPress={() => this.props.onToggleEdit(false)}> | |
<Text style={styles.doneText}>Save</Text> | |
</TouchableOpacity> | |
); | |
const removeButton = ( | |
<TouchableOpacity onPress={this.props.onRemove}> | |
<Text style={styles.destroy}>X</Text> | |
</TouchableOpacity> | |
); | |
return ( | |
<View style={styles.container}> | |
{this.props.editing ? editingTitleComponent : textTitleComponent} | |
{this.props.editing ? editingAmountComponent : textAmountComponent} | |
{this.props.editing ? doneButton : removeButton } | |
</View> | |
); | |
} | |
} | |
const styles = StyleSheet.create({ | |
container: { | |
padding: 10, | |
flexDirection: 'row', | |
alignItems: 'flex-start', | |
justifyContent: 'space-between', | |
}, | |
input: { | |
height: 100, | |
flex: 1, | |
fontSize: 24, | |
padding: 0, | |
color: '#4d4d4d', | |
}, | |
textWrap: { | |
flex: 1, | |
marginHorizontal: 10, | |
}, | |
done: { | |
borderRadius: 5, | |
borderWidth: 1, | |
borderColor: '#7be290', | |
padding: 7, | |
}, | |
doneText: { | |
color: '#4d4d4d', | |
fontSize: 20, | |
}, | |
text: { | |
flex: 1, | |
fontSize: 24, | |
color: '#141414', | |
}, | |
destroy: { | |
fontSize: 20, | |
color: '#cc9a9a', | |
}, | |
}); | |
export default Row; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment