diff --git a/RNTableView.xcodeproj/project.pbxproj b/RNTableView.xcodeproj/project.pbxproj index 83dfa03..c78beb8 100644 --- a/RNTableView.xcodeproj/project.pbxproj +++ b/RNTableView.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 0808A0331C67E9A60038993A /* RNReactModuleCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 0808A0321C67E9A60038993A /* RNReactModuleCell.m */; }; 872545E11BAAC85D00889249 /* JSONDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 872545D61BAAC85D00889249 /* JSONDataSource.m */; }; 872545E21BAAC85D00889249 /* RNCellView.m in Sources */ = {isa = PBXBuildFile; fileRef = 872545D81BAAC85D00889249 /* RNCellView.m */; }; 872545E31BAAC85D00889249 /* RNCellViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 872545DA1BAAC85D00889249 /* RNCellViewManager.m */; }; @@ -32,6 +33,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0808A0311C67E9A60038993A /* RNReactModuleCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNReactModuleCell.h; sourceTree = ""; }; + 0808A0321C67E9A60038993A /* RNReactModuleCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNReactModuleCell.m; sourceTree = ""; }; 872545D51BAAC85D00889249 /* JSONDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONDataSource.h; sourceTree = ""; }; 872545D61BAAC85D00889249 /* JSONDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSONDataSource.m; sourceTree = ""; }; 872545D71BAAC85D00889249 /* RNCellView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNCellView.h; sourceTree = ""; }; @@ -69,6 +72,8 @@ 872545D41BAAC85D00889249 /* RNTableView */ = { isa = PBXGroup; children = ( + 0808A0311C67E9A60038993A /* RNReactModuleCell.h */, + 0808A0321C67E9A60038993A /* RNReactModuleCell.m */, 872545D51BAAC85D00889249 /* JSONDataSource.h */, 872545D61BAAC85D00889249 /* JSONDataSource.m */, 872545D71BAAC85D00889249 /* RNCellView.h */, @@ -177,6 +182,7 @@ 872545E31BAAC85D00889249 /* RNCellViewManager.m in Sources */, 8799E1E01BF1152400AF9A67 /* RNTableFooterViewManager.m in Sources */, 872545E51BAAC85D00889249 /* RNTableViewCell.m in Sources */, + 0808A0331C67E9A60038993A /* RNReactModuleCell.m in Sources */, 8799E1DD1BF114F900AF9A67 /* RNTableFooterView.m in Sources */, 8799E1E31BF117F600AF9A67 /* RNTableHeaderView.m in Sources */, 8799E1E61BF1181400AF9A67 /* RNTableHeaderViewManager.m in Sources */, diff --git a/RNTableView/RNReactModuleCell.h b/RNTableView/RNReactModuleCell.h new file mode 100644 index 0000000..81886ca --- /dev/null +++ b/RNTableView/RNReactModuleCell.h @@ -0,0 +1,13 @@ + +#import + +//Use react-native root views as reusable cells returned from cellForRowAtIndexPath. + +@interface RNReactModuleCell : UITableViewCell { +} + +- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier bridge:(RCTBridge*) bridge data:(NSDictionary*)data indexPath:(NSIndexPath*)indexPath reactModule:(NSString*)reactModule; + +-(void)setUpAndConfigure:(NSDictionary*)data bridge:(RCTBridge*)bridge indexPath:(NSIndexPath*)indexPath reactModule:(NSString*)reactModule; + +@end \ No newline at end of file diff --git a/RNTableView/RNReactModuleCell.m b/RNTableView/RNReactModuleCell.m new file mode 100644 index 0000000..49860e9 --- /dev/null +++ b/RNTableView/RNReactModuleCell.m @@ -0,0 +1,54 @@ +// +// RNReactModuleCell.m +// RNTableView +// +// Created by Anna Berman on 2/6/16. +// Copyright © 2016 Pavlo Aksonov. All rights reserved. +// + +#import +#import "RNReactModuleCell.h" + +@implementation RNReactModuleCell { + RCTRootView *_rootView; +} + +- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier bridge:(RCTBridge*) bridge data:(NSDictionary*)data indexPath:(NSIndexPath*)indexPath reactModule:(NSString*)reactModule +{ + self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; + if (self) { + [self setUpAndConfigure:data bridge:bridge indexPath:indexPath reactModule:reactModule]; + } + return self; +} + +-(NSDictionary*) toProps:(NSDictionary *)data indexPath:(NSIndexPath*)indexPath { + return @{@"data":data, @"section":[[NSNumber alloc] initWithLong:indexPath.section], @"row":[[NSNumber alloc] initWithLong:indexPath.row]}; +} + +-(void)setUpAndConfigure:(NSDictionary*)data bridge:(RCTBridge*)bridge indexPath:(NSIndexPath*)indexPath reactModule:(NSString*)reactModule{ + NSDictionary *props = [self toProps:data indexPath:indexPath]; + if (_rootView == nil) { + //Create the mini react app that will populate our cell. This will be called from cellForRowAtIndexPath + _rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:reactModule initialProperties:props]; + if (data[@"width"]) { + CGRect contentViewFrame = self.contentView.frame; + contentViewFrame.size.width = ((NSNumber*)data[@"width"]).floatValue; + self.contentView.frame = contentViewFrame; + } + [self.contentView addSubview:_rootView]; + _rootView.frame = self.contentView.frame; + } else { + //Ask react to re-render us with new data + _rootView.appProperties = props; + } + + //The application will be unmounted in javascript when the cell/rootview is destroyed +} + +-(void)prepareForReuse { + [super prepareForReuse]; + //TODO prevent stale data flickering +} + +@end diff --git a/RNTableView/RNTableView.h b/RNTableView/RNTableView.h index f8cd9d9..ddcc478 100644 --- a/RNTableView/RNTableView.h +++ b/RNTableView/RNTableView.h @@ -8,6 +8,7 @@ #import @class RCTEventDispatcher; +@class RCTBridge; @protocol RNTableViewDatasource @@ -21,7 +22,7 @@ @interface RNTableView : UIView -- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher bridge:(RCTBridge*) bridge NS_DESIGNATED_INITIALIZER; @property (nonatomic, copy) NSMutableArray *sections; @property (nonatomic, copy) NSArray *additionalItems; @@ -43,6 +44,7 @@ @property (nonatomic, assign) UITableViewStyle tableViewStyle; @property (nonatomic, assign) UITableViewCellStyle tableViewCellStyle; @property (nonatomic, assign) UITableViewCellEditingStyle tableViewCellEditingStyle; +@property (nonatomic, assign) BOOL showsDragIconWhenEditing; @property (nonatomic, assign) UITableViewCellSeparatorStyle separatorStyle; @property (nonatomic, strong) UIFont *font; @property (nonatomic, strong) UIFont *headerFont; @@ -58,5 +60,6 @@ @property (nonatomic) BOOL autoFocus; @property (nonatomic) BOOL allowsToggle; @property (nonatomic) BOOL allowsMultipleSelection; +@property (nonatomic) NSString *reactModuleForCell; @end diff --git a/RNTableView/RNTableView.m b/RNTableView/RNTableView.m index 987b516..405ad5e 100644 --- a/RNTableView/RNTableView.m +++ b/RNTableView/RNTableView.m @@ -15,6 +15,7 @@ #import "RNCellView.h" #import "RNTableFooterView.h" #import "RNTableHeaderView.h" +#import "RNReactModuleCell.h" @interface RNTableView() { id datasource; @@ -25,9 +26,11 @@ @interface RNTableView() { @end @implementation RNTableView { + RCTBridge *_bridge; RCTEventDispatcher *_eventDispatcher; NSArray *_items; NSMutableArray *_cells; + NSString *_reactModuleCellReuseIndentifier; } -(void)setEditing:(BOOL)editing { @@ -66,11 +69,18 @@ - (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex } } -- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher +- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher bridge:(RCTBridge*)bridge { RCTAssertParam(eventDispatcher); if ((self = [super initWithFrame:CGRectZero])) { + + //RCTRootView setAppProperties and initWithBridge call "_bridge.batchedBridge" which will return nil because the bridge that gets passed to this constructor is *already* the batched bridge. + //So we have to create a separate (parent) _bridge here ourselves. + _bridge = [[RCTBridge alloc] initWithBundleURL:bridge.bundleURL + moduleProvider:nil + launchOptions:nil]; + _eventDispatcher = eventDispatcher; _cellHeight = 44; _cells = [NSMutableArray array]; @@ -148,6 +158,8 @@ - (void)createTableView { _tableView.tableHeaderView = view; _tableView.tableFooterView = view; _tableView.separatorStyle = self.separatorStyle; + _reactModuleCellReuseIndentifier = @"ReactModuleCell"; + [_tableView registerClass:[RNReactModuleCell class] forCellReuseIdentifier:_reactModuleCellReuseIndentifier]; [self addSubview:_tableView]; } - (void)tableView:(UITableView *)tableView willDisplayFooterView:(nonnull UIView *)view forSection:(NSInteger)section { @@ -279,6 +291,7 @@ - (void)setSections:(NSArray *)sections if(selectedIndex == -1) selectedIndex = [items count]; itemData[@"selected"] = @YES; + found = YES; } [items addObject:itemData]; @@ -312,20 +325,36 @@ -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger) } return count; } + +-(UITableViewCell*)setupReactModuleCell:(UITableView *)tableView data:(NSDictionary*)data indexPath:(NSIndexPath *)indexPath { + RNReactModuleCell *cell = [tableView dequeueReusableCellWithIdentifier:_reactModuleCellReuseIndentifier]; + if (cell == nil) { + cell = [[RNReactModuleCell alloc] initWithStyle:self.tableViewCellStyle reuseIdentifier:_reactModuleCellReuseIndentifier bridge: _bridge data:data indexPath:indexPath reactModule:_reactModuleForCell]; + } else { + [cell setUpAndConfigure:data bridge:_bridge indexPath:indexPath reactModule:_reactModuleForCell]; + } + return cell; +} + -(UITableViewCell* )tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"]; + UITableViewCell *cell = nil; NSDictionary *item = [self dataForRow:indexPath.item section:indexPath.section]; // check if it is standard cell or user-defined UI - if (![self hasCustomCells:indexPath.section]){ + if ([self hasCustomCells:indexPath.section]){ + cell = ((RNCellView *)_cells[indexPath.section][indexPath.row]).tableViewCell; + ((RNTableViewCell *)cell).showsDragIconWhenEditing = self.showsDragIconWhenEditing; + } else if (self.reactModuleForCell != nil && ![self.reactModuleForCell isEqualToString:@""]) { + cell = [self setupReactModuleCell:tableView data:item indexPath:indexPath]; + } else { + cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:self.tableViewCellStyle reuseIdentifier:@"Cell"]; } cell.textLabel.text = item[@"label"]; cell.detailTextLabel.text = item[@"detail"]; - } else { - cell = ((RNCellView *)_cells[indexPath.section][indexPath.row]).tableViewCell; } + if (item[@"selected"] && [item[@"selected"] intValue]){ cell.accessoryType = UITableViewCellAccessoryCheckmark; } else if ([item[@"arrow"] intValue]) { @@ -354,7 +383,8 @@ -(NSMutableDictionary *)dataForRow:(NSInteger)row section:(NSInteger)section { -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { if (![self hasCustomCells:indexPath.section]){ - return _cellHeight; + NSNumber *styleHeight = _sections[indexPath.section][@"items"][indexPath.row][@"height"]; + return styleHeight.floatValue ?: _cellHeight; } else { RNCellView *cell = (RNCellView *)_cells[indexPath.section][indexPath.row]; CGFloat height = cell.componentHeight; diff --git a/RNTableView/RNTableViewCell.h b/RNTableView/RNTableViewCell.h index 85e4e21..8246547 100644 --- a/RNTableView/RNTableViewCell.h +++ b/RNTableView/RNTableViewCell.h @@ -13,5 +13,6 @@ @interface RNTableViewCell : UITableViewCell @property (nonatomic, weak) RNCellView *cellView; +@property (nonatomic, assign) BOOL showsDragIconWhenEditing; @end diff --git a/RNTableView/RNTableViewCell.m b/RNTableView/RNTableViewCell.m index 26b957b..4c81857 100644 --- a/RNTableView/RNTableViewCell.m +++ b/RNTableView/RNTableViewCell.m @@ -18,6 +18,22 @@ -(void)setCellView:(RNCellView *)cellView { -(void)setFrame:(CGRect)frame { [super setFrame:frame]; [_cellView setFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)]; + + if (!self.showsDragIconWhenEditing && self.editing) { + static NSString *reorderControlClass = @"UITableViewCellReorderControl"; + for (UIView* view in self.subviews) { + if ([[[view class] description] isEqualToString:reorderControlClass]) { + view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 2.0, 2.0); // expands hit area somewhat + for (UIImageView* cellGrip in view.subviews) + { + if ([cellGrip isKindOfClass:[UIImageView class]]) { + [cellGrip setImage:nil]; + } + } + break; + } + } + } } @end diff --git a/RNTableView/RNTableViewManager.m b/RNTableView/RNTableViewManager.m index 245be24..292d7c4 100644 --- a/RNTableView/RNTableViewManager.m +++ b/RNTableView/RNTableViewManager.m @@ -16,7 +16,7 @@ @implementation RNTableViewManager RCT_EXPORT_MODULE() - (UIView *)view { - return [[RNTableView alloc] initWithEventDispatcher:self.bridge.eventDispatcher]; + return [[RNTableView alloc] initWithEventDispatcher:self.bridge.eventDispatcher bridge:self.bridge]; } RCT_EXPORT_VIEW_PROPERTY(sections, NSArray) @@ -41,6 +41,7 @@ - (UIView *)view RCT_EXPORT_VIEW_PROPERTY(moveWithinSectionOnly, BOOL) RCT_EXPORT_VIEW_PROPERTY(allowsToggle, BOOL) RCT_EXPORT_VIEW_PROPERTY(allowsMultipleSelection, BOOL) +RCT_EXPORT_VIEW_PROPERTY(showsDragIconWhenEditing, BOOL) RCT_CUSTOM_VIEW_PROPERTY(tableViewStyle, UITableViewStyle, RNTableView) { @@ -56,6 +57,11 @@ - (UIView *)view [view setTableViewCellEditingStyle:[RCTConvert NSInteger:json]]; } +/*Each cell is a separate app, multiple cells share the app/module name*/ +RCT_CUSTOM_VIEW_PROPERTY(reactModuleForCell, NSString*, RNTableView) { + [view setReactModuleForCell:[RCTConvert NSString:json]]; +} + RCT_CUSTOM_VIEW_PROPERTY(contentInset, UIEdgeInsets, RNTableView) { [view setContentInset:[RCTConvert UIEdgeInsets:json]]; } diff --git a/examples/TableViewDemo/index.ios.js b/examples/TableViewDemo/index.ios.js index 094d95d..5d3544e 100755 --- a/examples/TableViewDemo/index.ios.js +++ b/examples/TableViewDemo/index.ios.js @@ -1,20 +1,21 @@ 'use strict'; var React = require('react-native'); -var { AppRegistry, Text, Dimensions,View } = React; +var { AppRegistry, Text, Dimensions, View, TouchableHighlight, TextInput } = React; var TableView = require('react-native-tableview'); var Section = TableView.Section; var Item = TableView.Item; var Cell = TableView.Cell; var {Actions, Router, Route, Schema, Animations} = require('react-native-router-flux'); var NavigationBar = require('react-native-navbar'); +var Firebase = require('firebase'); class NavBar extends React.Component { render(){ return + {...this.props} /> } } class Example1 extends React.Component { @@ -112,6 +113,286 @@ class Example3 extends React.Component { } } +//Similar to example 2 but use "TableViewExampleCell" reusable cells +class ReusableCellExample1 extends React.Component { + // list spanish provinces and add 'All states' item at the beginning + render() { + var country = "ES"; + return ( + alert(JSON.stringify(event))}> + All states + + ); + } +} + +class ReusableCellExample2 extends React.Component { + render(){ + var numAdditionaItems = 1000; + var moreItems = []; + for (var i = 0; i < numAdditionaItems; ++i) { + moreItems.push(i); + } + return ( + alert(JSON.stringify(event))}> +
+ Item 1 + Item 2 + Item 3 + Item 4 + Item 5 + Item 6 + Item 7 + Item 8 + Item 9 + Item 10 + Item 11 + Item 12 + Item 13 + Item 14 + Item 15 + Item 16 +
+
+ Item 1 + Item 2 + Item 3 +
+
+ Item 1 + Item 2 + Item 3 +
+
+ {moreItems.map((i)=>{i+1})} +
+
+ ); + } +} + +class FirebaseExample extends React.Component { + constructor(props) { + super(props); + this.state = {data:null}; + this.reactCellModule = "DinosaurCellExample"; + this.firebaseLocation = "https://dinosaur-facts.firebaseio.com/dinosaurs"; + this.propPrefix = "dinosaur"; + } + componentDidMount() { + var dinData = null; + var self = this; + this.ref = new Firebase(this.firebaseLocation); + this.ref.on('value', function(snapshot) { + self.setState({data:snapshot.val()}); + }); + } + componentWillUnmount() { + this.ref.off(); + } + renderItem(itemData, key, index) { + //TODO passing itemData={itemData} doesn't seem to work... so pass all data props with a prefix to make sure they don't clash + //with other props + var item = {}; + Object.keys(itemData||{}).forEach(k => { + item[this.propPrefix+k] = itemData[k]; + }); + item[this.propPrefix+"key"] = key; + + return (); + } + render() { + var data = this.state.data; + if (!data) { + return NO DATA + } + + var self = this; + var items = Object.keys(data).map((key,index)=>self.renderItem(data[key], key, index)); + + return ( + + All Items + alert(JSON.stringify(event))}> +
+ {items} +
+
+
+ ); + } +} + + +class CustomEditableExample extends React.Component { + constructor(props) { + super(props); + this.state = {data:null,editing:false,text:""}; + this.reactCellModule = "TableViewExampleCell2"; + } + onExternalData(data) { + var self = this; + if (self.state.editing) { + console.warn("Ignoring update from firebase while editing data locally"); + } else { + self.setState({data:data}); + } + } + editOrSave() { + if (this.state.editing) { + //Save edited data + + var self = this; + var newData = (this.dataItemKeysBeingEdited || []).map(itemKey=>self.preEditData[itemKey]); + this.dataItemKeysBeingEdited = null; + this.preEditData = null; + + this.setState({editing: false, data: newData}, function() { + //Simulate saving data remotely and getting a data-changed callback + setTimeout(()=> self.onExternalData(newData), 2); + }); + } else { + this.preEditData = Object.assign({}, this.state.data); + //Must be same ordering as used in rendering items + this.dataItemKeysBeingEdited = Object.keys(this.state.data || {}); + this.setState({editing: true}); + } + } + cancelEditing() { + var data = this.preEditData; + this.dataItemKeysBeingEdited = null; + this.preEditData = null; + var self = this; + + self.setState({editing: false, data: data}); + } + moveItem(info) { + if (!this.dataItemKeysBeingEdited || info.sourceIndex >= this.dataItemKeysBeingEdited.length + || info.destinationIndex >= this.dataItemKeysBeingEdited.length) { + console.error("moved row source/destination indices are out of range"); + return; + } + var itemKey = this.dataItemKeysBeingEdited[info.sourceIndex]; + this.dataItemKeysBeingEdited.splice(info.sourceIndex, 1); + this.dataItemKeysBeingEdited.splice(info.destinationIndex, 0, itemKey); + + var self = this; + var newData = (this.dataItemKeysBeingEdited || []).map(itemKey=>self.preEditData[itemKey]); + this.setState({data: newData}); + } + deleteItem(info) { + if (!this.dataItemKeysBeingEdited || info.selectedIndex >= this.dataItemKeysBeingEdited.length) { + console.error("deleted row index is out of range"); + return; + } + this.dataItemKeysBeingEdited.splice(info.selectedIndex, 1); + + var self = this; + var newData = (this.dataItemKeysBeingEdited || []).map(itemKey=>self.preEditData[itemKey]); + this.setState({data: newData}); + } + addItem() { + var {text} = this.state; + if (!text) return; + var self = this; + + //Simulate saving data remotely and getting a data-changed callback + setTimeout(()=>self.onExternalData(!this.state.data?[text]:[...(this.state.data), text]), 2); + + //clear text & hide keyboard + this.setState({text:""}); + this.refs.addTextInput.blur(); + } + onChange(info) { + if (info.mode == 'move') { + this.moveItem(info); + } else if (info.mode == 'delete') { + this.deleteItem(info); + } else { + console.error("unknown change mode: "+info.mode); + } + } + renderItem(itemData, key, index) { + return ( + + ); + } + getNavProps() { + var self = this; + var navProps = { + title:{title:"Custom Editable"}, + rightButton: { + title: (this.state.editing? 'Save':'Edit'), + handler: function onNext() { + self.editOrSave(); + } + } + }; + navProps.leftButton = { + title: (this.state.editing?'Cancel':'Back'), + handler: function onNext() { + if (self.state.editing) + self.cancelEditing(); + else { + Actions.pop(); + } + } + }; + return navProps; + } + getAddItemRow() { + return ( + + this.setState({text:text})} + value={this.state.text} + /> + + {this.addItem()}} + style={{borderRadius:5, width:100,backgroundColor:"red",alignItems:"center",justifyContent:"center"}}> + Add + + + ); + } + render() { + var {data, editing} = this.state; + if (!data) { + data = {}; + } + + var self = this; + var items = Object.keys(data).map((key,index)=>self.renderItem(data[key], key, index)); + + return ( + + + + + {!editing && this.getAddItemRow()} + + +
+ {items} +
+
+
+ ); + } +} + class Edit extends React.Component { constructor(props){ super(props); @@ -124,23 +405,67 @@ class Edit extends React.Component { self.setState({editing: !self.state.editing})}/> alert(JSON.stringify(event))} onChange={(event) => alert("CHANGED:"+JSON.stringify(event))}> -
- Item 1 - Item 2 - Item 3 - Item 4 - Item 5 - Item 6 - Item 7 - Item 8 + onPress={(event) => alert(JSON.stringify(event))} onChange={(event) => alert("CHANGED:"+JSON.stringify(event))}> +
+ Item 1 + Item 2 + Item 3 + Item 4 + Item 5 + Item 6 + Item 7 + Item 8 +
+ + + ); + } +} +class ListViewExample extends React.Component { + constructor(props){ + super(props); + this.numAdditionaItems = 1000; + this.data = {}; + for (var i = 0; i < this.numAdditionaItems; ++i) { + this.data[i] = i; + } + this.state = {dataSource: new React.ListView.DataSource({ + rowHasChanged: (r1, r2) => r1 !== r2 + })}; + } + render() { + const data = this.data; + return ( + alert("item:"+k+", "+data[k])}> data: {data[k]}} + /> + ); + } +} + +class LargeTableExample extends React.Component { + render() { + var numAdditionaItems = 1000; + var items = []; + for (var i = 0; i < numAdditionaItems; ++i) { + items.push(i); + } + return ( + alert(JSON.stringify(event))}> +
+ {items.map((i)=>{i+1})}
- ); } } + class Launch extends React.Component { constructor(props) { super(props); @@ -158,6 +483,12 @@ class Launch extends React.Component { Example with app bundle JSON data Example with multiple sections Example with editing mode + Reusable Cell Example 1 + Reusable Custom Cells + Firebase Example + Large ListView (scroll memory growth) + Reusable Large TableView Example + Custom Editing Example
); @@ -173,11 +504,90 @@ class TableViewExample extends React.Component { - + + + + + + + ); } } +//Should be pure... setState on top-level component doesn't seem to work +class TableViewExampleCell extends React.Component { + render(){ + var style = {borderColor:"#aaaaaa", borderWidth:1, borderRadius:3}; + //cell height is passed from child of tableview and native code passes it back up to javascript in "app params" for the cell. + //This way our component will fill the full native table cell height. + if (this.props.data.height !== undefined) { + style.height = this.props.data.height; + } else { + style.flex = 1; + } + if (this.props.data.backgroundColor !== undefined) { + style.backgroundColor = this.props.data.backgroundColor; + } + return (section:{this.props.section},row:{this.props.row},label:{this.props.data.label}); + } +} + +//Should be pure... setState on top-level component doesn't seem to work +class TableViewExampleCell2 extends React.Component { + render(){ + var style = {}; + //cell height is passed from child of tableview and native code passes it back up to javascript in "app params" for the cell. + //This way our component will fill the full native table cell height. + if (this.props.data.height !== undefined) { + style.height = this.props.data.height; + } else { + style.flex = 1; + } + if (this.props.data.backgroundColor !== undefined) { + style.backgroundColor = this.props.data.backgroundColor; + } + return ({this.props.data.label}); + } +} + +//Should be pure... setState on top-level component doesn't seem to work +class DinosaurCellExample extends React.Component { + yearsAgoInMil(num) { + return ((-1 * num)/1000000)+" million years ago"; + } + render(){ + var style = {}; + //cell height is passed from child of tableview and native code passes it back up to javascript in "app params" for the cell. + //This way our component will fill the full native table cell height. + if (this.props.data.height !== undefined) { + style.height = this.props.data.height; + } + if (this.props.data.backgroundColor !== undefined) { + style.backgroundColor = this.props.data.backgroundColor; + } + style.borderColor = "grey"; + style.borderRadius = 0.02; + + var appeared = this.yearsAgoInMil(this.props.data.dinosaurappeared); + var vanished = this.yearsAgoInMil(this.props.data.dinosaurvanished); + return ( + Name: {this.props.data.dinosaurkey} + Order:{this.props.data.dinosaurorder} + Appeared: {appeared} + Vanished: {vanished} + Height: {this.props.data.dinosaurheight} + Length: {this.props.data.dinosaurlength} + Weight: {this.props.data.dinosaurweight} + ); + } +} + + + AppRegistry.registerComponent('TableViewExample', () => TableViewExample); +AppRegistry.registerComponent('TableViewExampleCell', () => TableViewExampleCell); +AppRegistry.registerComponent('TableViewExampleCell2', () => TableViewExampleCell2); +AppRegistry.registerComponent('DinosaurCellExample', () => DinosaurCellExample); diff --git a/examples/TableViewDemo/package.json b/examples/TableViewDemo/package.json index 66d6612..d0f5d2c 100644 --- a/examples/TableViewDemo/package.json +++ b/examples/TableViewDemo/package.json @@ -6,9 +6,11 @@ "start": "node_modules/react-native/packager/packager.sh" }, "dependencies": { - "react-native": "^0.14.2", - "react-native-navbar": "^0.8.2", - "react-native-router-flux": "^0.3.0", - "react-native-tableview": "1.4.1" + "firebase": "^2.4.0", + "react-native": "^0.19.0", + "react-native-navbar": "^1.2.0", + "react-native-router-flux": "^2.1.8", + "react-native-tableview": "1.4.2", + "react-native-tabs": "^0.1.10" } } diff --git a/index.js b/index.js index e3216f4..2047c20 100644 --- a/index.js +++ b/index.js @@ -50,6 +50,13 @@ var TableView = React.createClass({ * @platform ios */ scrollIndicatorInsets: React.EdgeInsetsPropType, + showsDragIconWhenEditing: React.PropTypes.bool, + }, + + getDefaultProps() { + return { + showsDragIconWhenEditing: true, + }; }, getInitialState: function() { @@ -125,7 +132,7 @@ var TableView = React.createClass({ additionalItems={this.state.additionalItems} tableViewStyle={TableView.Consts.Style.Plain} tableViewCellStyle={TableView.Consts.CellStyle.Subtitle} - tableViewCellEditingStyle={TableView.Consts.CellEditingStyle.Delete} + showsDragIconWhenEditing={this.props.showsDragIconWhenEditing} separatorStyle={TableView.Consts.SeparatorStyle.Line} scrollIndicatorInsets={this.props.contentInset} {...this.props}