Преглед изворни кода

[v2] add onSearchBarCancelPressed Event (#3390)

Summary: This adds a new event `onSearchBarCancelPressed` that fires
when the user presses the cancel button on the search bar in the
navigation bar. `onSearchBarUpdated` does indirectly fire when the user
presses cancel, as the query will be reset to an empty string, but it is
not possible to distinguish those events from the user simply resetting
the query. My use case for this is to have a view that appears on top of
the normal content of the view controller when the search bar is
focused.

Test Plan: ran `npm run test-js` and confirmed added JS unit tests
passed.

I also tested this with my app, and confirmed the
`onSearchBarCancelPressed` method was called when I pressed the cancel
button.
Ben Hiller пре 6 година
родитељ
комит
87d1be7469

+ 10
- 0
docs/docs/events.md Прегледај датотеку

103
   }
103
   }
104
 }
104
 }
105
 ```
105
 ```
106
+
107
+## onSearchBarCancelPressed (iOS 11+ only)
108
+Called when the cancel button on the SearchBar from NavigationBar gets pressed.
109
+```js
110
+class MyComponent extends Component {
111
+  onSearchBarCancelPressed() {
112
+
113
+  }
114
+}
115
+```

+ 2
- 0
lib/ios/RNNEventEmitter.h Прегледај датотеку

22
 
22
 
23
 -(void)sendOnSearchBarUpdated:(NSString *)componentId text:(NSString*)text isFocused:(BOOL)isFocused;
23
 -(void)sendOnSearchBarUpdated:(NSString *)componentId text:(NSString*)text isFocused:(BOOL)isFocused;
24
 
24
 
25
+-(void)sendOnSearchBarCancelPressed:(NSString *)componentId;
26
+
25
 @end
27
 @end

+ 6
- 0
lib/ios/RNNEventEmitter.m Прегледај датотеку

62
 												  @"isFocused": @(isFocused)}}];
62
 												  @"isFocused": @(isFocused)}}];
63
 }
63
 }
64
 
64
 
65
+- (void)sendOnSearchBarCancelPressed:(NSString *)componentId {
66
+	[self send:navigationEvent body:@{@"name": @"searchBarCancelPressed",
67
+									  @"params": @{
68
+											  @"componentId": componentId}}];
69
+}
70
+
65
 - (void)addListener:(NSString *)eventName {
71
 - (void)addListener:(NSString *)eventName {
66
 	[super addListener:eventName];
72
 	[super addListener:eventName];
67
 	if ([eventName isEqualToString:onAppLaunched]) {
73
 	if ([eventName isEqualToString:onAppLaunched]) {

+ 1
- 1
lib/ios/RNNRootViewController.h Прегледај датотеку

13
 
13
 
14
 typedef void (^RNNReactViewReadyCompletionBlock)(void);
14
 typedef void (^RNNReactViewReadyCompletionBlock)(void);
15
 
15
 
16
-@interface RNNRootViewController : UIViewController	<RNNRootViewProtocol, UIViewControllerPreviewingDelegate, UISearchResultsUpdating>
16
+@interface RNNRootViewController : UIViewController	<RNNRootViewProtocol, UIViewControllerPreviewingDelegate, UISearchResultsUpdating, UISearchBarDelegate>
17
 
17
 
18
 @property (nonatomic, strong) RNNNavigationOptions* options;
18
 @property (nonatomic, strong) RNNNavigationOptions* options;
19
 @property (nonatomic, strong) RNNEventEmitter *eventEmitter;
19
 @property (nonatomic, strong) RNNEventEmitter *eventEmitter;

+ 4
- 0
lib/ios/RNNRootViewController.m Прегледај датотеку

105
 									isFocused:searchController.searchBar.isFirstResponder];
105
 									isFocused:searchController.searchBar.isFirstResponder];
106
 }
106
 }
107
 
107
 
108
+- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
109
+	[self.eventEmitter sendOnSearchBarCancelPressed:self.componentId];
110
+}
111
+
108
 - (void)viewDidLoad {
112
 - (void)viewDidLoad {
109
 	[super viewDidLoad];
113
 	[super viewDidLoad];
110
 }
114
 }

+ 1
- 0
lib/ios/RNNTopBarOptions.m Прегледај датотеку

39
 			if ([viewController conformsToProtocol:@protocol(UISearchResultsUpdating)]) {
39
 			if ([viewController conformsToProtocol:@protocol(UISearchResultsUpdating)]) {
40
 				[search setSearchResultsUpdater:((UIViewController <UISearchResultsUpdating> *) viewController)];
40
 				[search setSearchResultsUpdater:((UIViewController <UISearchResultsUpdating> *) viewController)];
41
 			}
41
 			}
42
+			search.searchBar.delegate = (id<UISearchBarDelegate>)viewController;
42
 			if (self.searchBarPlaceholder) {
43
 			if (self.searchBarPlaceholder) {
43
 				search.searchBar.placeholder = self.searchBarPlaceholder;
44
 				search.searchBar.placeholder = self.searchBarPlaceholder;
44
 			}
45
 			}

+ 14
- 0
lib/src/components/ComponentWrapper.test.tsx Прегледај датотеку

156
     const componentDidDisappearCallback = jest.fn();
156
     const componentDidDisappearCallback = jest.fn();
157
     const onNavigationButtonPressedCallback = jest.fn();
157
     const onNavigationButtonPressedCallback = jest.fn();
158
     const onSearchBarCallback = jest.fn();
158
     const onSearchBarCallback = jest.fn();
159
+    const onSearchBarCancelCallback = jest.fn();
159
 
160
 
160
     class MyLifecycleComponent extends MyComponent {
161
     class MyLifecycleComponent extends MyComponent {
161
       componentDidAppear() {
162
       componentDidAppear() {
173
       onSearchBarUpdated() {
174
       onSearchBarUpdated() {
174
         onSearchBarCallback();
175
         onSearchBarCallback();
175
       }
176
       }
177
+
178
+      onSearchBarCancelPressed() {
179
+        onSearchBarCancelCallback();
180
+      }
176
     }
181
     }
177
 
182
 
178
     it('componentDidAppear, componentDidDisappear and onNavigationButtonPressed are optional', () => {
183
     it('componentDidAppear, componentDidDisappear and onNavigationButtonPressed are optional', () => {
182
       expect(() => tree.getInstance()!.componentDidDisappear()).not.toThrow();
187
       expect(() => tree.getInstance()!.componentDidDisappear()).not.toThrow();
183
       expect(() => tree.getInstance()!.onNavigationButtonPressed()).not.toThrow();
188
       expect(() => tree.getInstance()!.onNavigationButtonPressed()).not.toThrow();
184
       expect(() => tree.getInstance()!.onSearchBarUpdated()).not.toThrow();
189
       expect(() => tree.getInstance()!.onSearchBarUpdated()).not.toThrow();
190
+      expect(() => tree.getInstance()!.onSearchBarCancelPressed()).not.toThrow();
185
     });
191
     });
186
 
192
 
187
     it('calls componentDidAppear on OriginalComponent', () => {
193
     it('calls componentDidAppear on OriginalComponent', () => {
215
       tree.getInstance()!.onSearchBarUpdated();
221
       tree.getInstance()!.onSearchBarUpdated();
216
       expect(onSearchBarCallback).toHaveBeenCalledTimes(1);
222
       expect(onSearchBarCallback).toHaveBeenCalledTimes(1);
217
     });
223
     });
224
+
225
+    it('calls onSearchBarCancelPressed on OriginalComponent', () => {
226
+      const NavigationComponent = ComponentWrapper.wrap(componentName, MyLifecycleComponent, store);
227
+      const tree = renderer.create(<NavigationComponent componentId={'component1'} />);
228
+      expect(onSearchBarCancelCallback).toHaveBeenCalledTimes(0);
229
+      tree.getInstance()!.onSearchBarCancelPressed();
230
+      expect(onSearchBarCancelCallback).toHaveBeenCalledTimes(1);
231
+    });
218
   });
232
   });
219
 });
233
 });

+ 6
- 0
lib/src/components/ComponentWrapper.tsx Прегледај датотеку

58
         }
58
         }
59
       }
59
       }
60
 
60
 
61
+      onSearchBarCancelPressed() {
62
+        if (this.originalComponentRef.onSearchBarCancelPressed) {
63
+          this.originalComponentRef.onSearchBarCancelPressed();
64
+        }
65
+      }
66
+
61
       render() {
67
       render() {
62
         return (
68
         return (
63
           <OriginalComponentClass
69
           <OriginalComponentClass

+ 24
- 1
lib/src/events/ComponentEventsObserver.test.ts Прегледај датотеку

19
       componentDidAppear: jest.fn(),
19
       componentDidAppear: jest.fn(),
20
       componentDidDisappear: jest.fn(),
20
       componentDidDisappear: jest.fn(),
21
       onNavigationButtonPressed: jest.fn(),
21
       onNavigationButtonPressed: jest.fn(),
22
-      onSearchBarUpdated: jest.fn()
22
+      onSearchBarUpdated: jest.fn(),
23
+      onSearchBarCancelPressed: jest.fn()
23
     };
24
     };
24
 
25
 
25
     store = new Store();
26
     store = new Store();
44
     expect(mockComponentRef.componentDidDisappear).toHaveBeenCalledTimes(0);
45
     expect(mockComponentRef.componentDidDisappear).toHaveBeenCalledTimes(0);
45
     expect(mockComponentRef.onNavigationButtonPressed).toHaveBeenCalledTimes(0);
46
     expect(mockComponentRef.onNavigationButtonPressed).toHaveBeenCalledTimes(0);
46
     expect(mockComponentRef.onSearchBarUpdated).toHaveBeenCalledTimes(0);
47
     expect(mockComponentRef.onSearchBarUpdated).toHaveBeenCalledTimes(0);
48
+    expect(mockComponentRef.onSearchBarCancelPressed).toHaveBeenCalledTimes(0);
47
     uut.registerForAllComponents();
49
     uut.registerForAllComponents();
48
     eventRegistry.registerComponentDidAppearListener.mock.calls[0][0](refId);
50
     eventRegistry.registerComponentDidAppearListener.mock.calls[0][0](refId);
49
     eventRegistry.registerComponentDidDisappearListener.mock.calls[0][0](refId);
51
     eventRegistry.registerComponentDidDisappearListener.mock.calls[0][0](refId);
52
     expect(mockComponentRef.componentDidDisappear).toHaveBeenCalledTimes(1);
54
     expect(mockComponentRef.componentDidDisappear).toHaveBeenCalledTimes(1);
53
     expect(mockComponentRef.onNavigationButtonPressed).toHaveBeenCalledTimes(0);
55
     expect(mockComponentRef.onNavigationButtonPressed).toHaveBeenCalledTimes(0);
54
     expect(mockComponentRef.onSearchBarUpdated).toHaveBeenCalledTimes(0);
56
     expect(mockComponentRef.onSearchBarUpdated).toHaveBeenCalledTimes(0);
57
+    expect(mockComponentRef.onSearchBarCancelPressed).toHaveBeenCalledTimes(0);
55
   });
58
   });
56
 
59
 
57
   it('bubbles onNavigationButtonPressed to component by id', () => {
60
   it('bubbles onNavigationButtonPressed to component by id', () => {
95
     expect(mockComponentRef.onSearchBarUpdated).toHaveBeenCalledWith('query', true);
98
     expect(mockComponentRef.onSearchBarUpdated).toHaveBeenCalledWith('query', true);
96
   });
99
   });
97
 
100
 
101
+  it('bubbles onSearchBarCancelPressed to component by id', () => {
102
+    const params = {
103
+      componentId: refId,
104
+    };
105
+    expect(mockComponentRef.onSearchBarCancelPressed).toHaveBeenCalledTimes(0);
106
+    uut.registerForAllComponents();
107
+
108
+    eventRegistry.registerNativeEventListener.mock.calls[0][0]('buttonPressed', params);
109
+    expect(mockComponentRef.onSearchBarCancelPressed).toHaveBeenCalledTimes(0);
110
+
111
+    eventRegistry.registerNativeEventListener.mock.calls[0][0]('searchBarCancelPressed', params);
112
+    expect(mockComponentRef.onSearchBarCancelPressed).toHaveBeenCalledTimes(1);
113
+
114
+    const paramsForUnexisted = {
115
+      componentId: 'NOT_EXISTED',
116
+    };
117
+    eventRegistry.registerNativeEventListener.mock.calls[0][0]('searchBarCancelPressed', paramsForUnexisted);
118
+    expect(mockComponentRef.onSearchBarCancelPressed).toHaveBeenCalledTimes(1);
119
+  });
120
+
98
   it('defensive unknown id', () => {
121
   it('defensive unknown id', () => {
99
     uut.registerForAllComponents();
122
     uut.registerForAllComponents();
100
     expect(() => {
123
     expect(() => {

+ 7
- 0
lib/src/events/ComponentEventsObserver.ts Прегледај датотеку

3
 
3
 
4
 const BUTTON_PRESSED_EVENT_NAME = 'buttonPressed';
4
 const BUTTON_PRESSED_EVENT_NAME = 'buttonPressed';
5
 const ON_SEARCH_BAR_UPDATED = 'searchBarUpdated';
5
 const ON_SEARCH_BAR_UPDATED = 'searchBarUpdated';
6
+const ON_SEARCH_BAR_CANCEL_PRESSED = 'searchBarCancelPressed';
6
 
7
 
7
 export class ComponentEventsObserver {
8
 export class ComponentEventsObserver {
8
   constructor(private eventsRegistry: EventsRegistry, private store: Store) {
9
   constructor(private eventsRegistry: EventsRegistry, private store: Store) {
44
         componentRef.onSearchBarUpdated(params.text, params.isFocused);
45
         componentRef.onSearchBarUpdated(params.text, params.isFocused);
45
       }
46
       }
46
     }
47
     }
48
+    if (name === ON_SEARCH_BAR_CANCEL_PRESSED) {
49
+      const componentRef = this.store.getRefForId(params.componentId);
50
+      if (componentRef && componentRef.onSearchBarCancelPressed) {
51
+        componentRef.onSearchBarCancelPressed();
52
+      }
53
+    }
47
   }
54
   }
48
 }
55
 }