I'm not sure if this is the right place to be asking this question, but I don't know where else to ask.
I have a chat view which when I click on the text box at the bottom, the listview is pushed up together with the whole page. Is there any way to make the list responsive to the keyboard and not push the top bar up when the keyboard appears?
The top bar is a custom element, it's not the actionbar.
14 Answers, 1 is accepted
I tested this scenario on my side while using very basic project with Label RadListView and TextField which are nested inside a GridLayout, however, when the Field is focused only this component has been moved up and the custom element and ListView keep in the same position.
For your convenience, I am attaching a screenshot from the simulator and the test project.
Regards,
nikolay.tsonev
Progress Telerik
Hello Nikolay,
This is how I have my project setup.
<
GridLayout
>
<
GridLayout
>
<
StackLayout
id
=
"application"
orientation
=
"vertical"
>
<
ink-actionbar
></
ink-actionbar
>
<
router-outlet
name
=
"sublayoutoutlet"
></
router-outlet
>
</
StackLayout
>
</
GridLayout
>
<
ink-loading
></
ink-loading
>
</
GridLayout
>
<
StackLayout
id
=
"messages-view"
orientation
=
"vertical"
>
<
GridLayout
rows
=
"*, auto"
>
<
RadListView
#radListView
class
=
"messages-list"
[items]="messages"
row
=
"0"
col
=
"0"
backgroundColor
=
"transparent"
(itemLoading)="onItemLoading($event)"
pullToRefresh
=
"true"
(pullToRefreshInitiated)="onPullToRefreshInitiated($event)">
<
ListViewStaggeredLayout
tkListViewLayout
spanCount
=
"1"
></
ListViewStaggeredLayout
>
<
ng-template
tkListItemTemplate
let-item
=
"item"
let-i
=
"index"
>
<
DockLayout
[stretchLastChild]=false>
<
Label
dock
=
"top"
class
=
"display-date"
[class.no-text]="!item.displayDate" [text]="item.displayDate"></
Label
>
<
TextView
[dock]="item.isMe ? 'right' : 'left'"
row
=
"1"
col
=
"0"
class
=
"message"
[class.me]="item.isMe"
[class.last]="messages.length - 1 === i" [text]="item.text" [editable]=false [isUserInteractionEnabled]=false></
TextView
>
</
DockLayout
>
</
ng-template
>
</
RadListView
>
<
GridLayout
class
=
"message-input"
rows
=
"auto"
columns
=
"*, auto"
row
=
"1"
col
=
"0"
>
<
StackLayout
>
<
TextView
#messageInput
hint
=
"Message Text"
[(ngModel)]="messageBody" (ngModelChange)="messageBodyChanged($event)"></
TextView
>
</
StackLayout
>
<
Button
row
=
"0"
col
=
"1"
class
=
"send-button"
[class.disabled]="sendDisabled"
text
=
"Send"
verticalAlignment
=
"center"
(tap)="sendMessage(messageBody)"></
Button
>
</
GridLayout
>
</
GridLayout
>
</
StackLayout
>
I also tried to do the same type of layout as in your sample project and it still didn't work.
<
GridLayout
id
=
"application"
rows
=
"auto, *, auto"
>
<
StackLayout
>
<
ink-actionbar
></
ink-actionbar
>
</
StackLayout
>
<
router-outlet
name
=
"sublayoutoutlet"
></
router-outlet
>
</
GridLayout
>
<
GridLayout
row
=
"1"
>
<
RadListView
row
=
"1"
#radListView
class
=
"messages-list"
[items]="messages"
row
=
"0"
col
=
"0"
backgroundColor
=
"transparent"
(itemLoading)="onItemLoading($event)"
pullToRefresh
=
"true"
(pullToRefreshInitiated)="onPullToRefreshInitiated($event)">
<
ListViewStaggeredLayout
tkListViewLayout
spanCount
=
"1"
></
ListViewStaggeredLayout
>
<
ng-template
tkListItemTemplate
let-item
=
"item"
let-i
=
"index"
>
<
DockLayout
[stretchLastChild]=false>
<
Label
dock
=
"top"
class
=
"display-date"
[class.no-text]="!item.displayDate" [text]="item.displayDate"></
Label
>
<
TextView
[dock]="item.isMe ? 'right' : 'left'"
row
=
"1"
col
=
"0"
class
=
"message"
[class.me]="item.isMe"
[class.last]="messages.length - 1 === i" [text]="item.text" [editable]=false [isUserInteractionEnabled]=false></
TextView
>
</
DockLayout
>
</
ng-template
>
</
RadListView
>
</
GridLayout
>
<
TextView
row
=
"2"
#messageInput
hint
=
"Message Text"
[(ngModel)]="messageBody" (ngModelChange)="messageBodyChanged($event)"></
TextView
>
Based on the application send my colleague Nikolay Y cone you can further simplify your Angular based layout. In your case you are using DockLayout inside the item template - you can use Grid or Stack and accommodate the l9ogic just as shown in the sample applcationOne important thing to do to explicitly set both rows and then row and col on every element to avoid miss interpretation.
e.g. basic example is demonstrating the technique with the explicit setting of rows and row and without using dock layout.
<
GridLayout
rows
=
"500, 100"
tkExampleTitle tkToggleNavButton>
<
RadListView
row
=
"0"
[items]="dataItems">
<
ng-template
tkListItemTemplate
let-item
=
"item"
>
<
StackLayout
orientation
=
"vertical"
>
<
Label
class
=
"nameLabel"
[text]="item.name"></
Label
>
<
Label
class
=
"descriptionLabel"
[text]="item.description"></
Label
>
</
StackLayout
>
</
ng-template
>
</
RadListView
>
<
TextView
row
=
"1"
[(ngModel)]="tvtext"
hint
=
"Enter some text"
class
=
"input input-border"
></
TextView
>
</
GridLayout
>
Regards,
Nikolay Iliev
Progress Telerik
Thanks for the response, Nikolay, but I was still not able to solve the problem.
I wasn't able to reproduce the errors in a fresh new app, but I continue to get the same error in my current project.
Based on the testing I did, I believe it has something to do with the page element not being initialized properly with the list view, but I'm not sure.
From this video, you can see how the first time I navigate to the component outlet, it is bugged, but if I scroll all the way up on the list then it will be un-bugged for next times.
One more insight is that if I use
{ path: '', pathMatch: 'full', redirectTo: '/sublayout/(sublayoutoutlet:messages/95/126)', }
instead of
{ path: '', pathMatch: 'full', redirectTo: '/layout/(layoutoutlet:dashboard)', }
then the list works as expected.
Here's the type of routing I am using: (Messages outlet is the one with the list-view)
{ path: '', pathMatch: 'full', redirectTo: '/layout/(layoutoutlet:dashboard)', },
{ path: 'layout', component: LayoutComponent, canActivate: [AuthGuard], children: [
{ path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard], outlet: 'layoutoutlet' }
]},
{ path: 'sublayout', component: SubLayoutComponent, canActivate: [AuthGuard], children: [
{ path: 'messages/:my_card_id/:other_card_id', component: MessageViewComponent, canActivate: [AuthGuard], outlet: 'sublayoutoutlet' }
]}
Actually, I was able to reproduce the error in a sample project. Still don't know exactly what causes it though.
The reason for your issue is the way you have structured your application using nested router-outlet. When you load the content of the router-outlet you are working with all the layouts that are contained within this outlet. So when the TextView requires the focus and the keyboard appears it will push all the content loaded in the outlet. If you want to dock the button and to prevent the keyboard from pushing the button and the listview then all you need to do is to place the docked button on a level with your router-outlet as done here.
Based on the test application you have sent in your last post here is the changes I have made to make it work.
test.html
<
StackLayout
backgrundColor
=
"green"
>
<
Button
text
=
"Go back"
[nsRouterLink]="['/main', { outlets: { outlet: ['test']}}]"></
Button
>
<
router-outlet
name
=
"outlet"
></
router-outlet
>
</
StackLayout
>
test-inner.html
<
GridLayout
rows
=
"*, auto"
columns
=
""
>>
<
GridLayout
row
=
"0"
backgroundColor
=
"blue"
>
<
RadListView
[items]="dataItems">
<
ListViewLinearLayout
tkListViewLayout
scrollDirection
=
"Vertical"
> </
ListViewLinearLayout
>
<
ng-template
let-item
=
"item"
tkListItemTemplate>
<
StackLayout
orientation
=
"vertical"
>
<
Label
fontSize
=
"20"
text
=
"{{ item.name }}"
></
Label
>
<
Label
fontSize
=
"14"
text
=
"{{ item.description }}"
></
Label
>
</
StackLayout
>
</
ng-template
>
</
RadListView
>
</
GridLayout
>
<
TextField
row
=
"1"
hint
=
"Enter some text"
text
=
""
></
TextField
>
</
GridLayout
>
Regards,
Nikolay Iliev
Progress Telerik
Hi Nikolay,
I copy and pasted the changes you made to my sample project, and it still did not work for me. Did you try restarting the app and seeing if it works a second time?
Here's a video of me running the updated sample project:
Indeed I was able to reproduce the issue again on the second run after I have rebuilt my test application.
It appears that this is inconsistent behavior when a software keyboard is focused within a router-outlet. I have logged this as an issue here. You can track the issue for suggestions and fixes - after some research on the topic with the nativescript-angular developers, it appears that there are some structural changes related to router-outlet which will be released in the upcoming versions. Once the new release is official, I will retest this scenario and update the information in te linked issue.
Meanwhile, at this very moment, the only workaround is to switch the places of your RadListView and TextField (so that the TextField will load first) and thus the keyboard stops pushing the content.
Note: In my test application the current structure is also not pushing the content, but I suspect that the issue is inconsistent - perhaps you can try at your side my configuration and if does not work as expected - try to change places of the TextFiel and RadListView here
Regards,
Nikolay Iliev
Progress Telerik
Hello Nikolay,
Regarding this issue, I mentioned before how the issue was fixed if you focused the text field and initiated a pull to refresh action.
So, I confirmed that removing the line 'this._android.addBehavior(this._pullToRefreshBehavior);' on listview.android.js makes the above statement not true.
Is there any way to call this 'addBehavior' method or initiate a pull to refresh manually from the angular app when the RadListView is initiated? I believe that would be a workaround for this issue.
Thank you.
We are currently investigating the reason behind this issue and currently, it seems that the problem is caused by router-outlet not being able to occupy the space that it was given in test.html (in your case you are using StackLayout but even if using GridLayout with hard coded rows values it will again not span on the wanted space). At this moment we don't have a feature that can trigger the pullToRefresh from code-behind but I must point out that even if this is possible it won't be a good workaround as in fact the issue is related not to the RadListView and pullToRefresh functionality but to the way router-outlet works.
Please keep track on the linked issue where our developers will seek for a solution for this specific scenario.
Meanwhile, as a solution, you can use loadChildren instead of children to structure your routes.
When using loadChildren the content in the router-outlet will load and occupy the available space as expected and the software keyboard won't push the outer content. This would require for you to implement lazy loading for your inner routes and create separate modules for each lazily loaded module.
Real life example demonstrating exactly your scenario but using loadChildren can be found in the nested-routers example in our nativescript-sdk-examples-ng application. You can test this example by implementing a text field in the content of the router-outlet and it won't push the outer buttons.
Implementing the lazy loading pattern will not only solve this issue but will also improve the overall startup time of bigger application you won't load all the modules at the very beginning of your application start but only when needed.
Few things that are important for Lazy Loading (based on this application)
- you need to provide the оур NGModuleFactoryLoader as done here
mport { NativeScriptRouterModule, NSModuleFactoryLoader } from
"nativescript-angular/router"
;
@NgModule({
schemas: [NO_ERRORS_SCHEMA],
declarations: [
AppComponent,
],
bootstrap: [AppComponent],
imports: [
NativeScriptModule,
NativeScriptRouterModule,
NativeScriptRouterModule.forRoot(routes),
],
providers: [
ModalDialogService,
{ provide: NgModuleFactoryLoader, useClass: NSModuleFactoryLoader }
]
})
- when creating your routes use not children but loadChildren as done here and then add the main route config as usual for each lazily loaded child route. Note the following line
loadChildren:
"./routing/routing-examples.module#RoutingExamplesModule"
,
and then we have the special symbol #
followed by the name of the exported module RoutingExamplesModule
- then for your lazy loaded module (e.g. this) use children as done here.
We are currently working on a blog post explaining the new lazy loading pattern step by step (as done in nativescript-sdk-example-ng where we are loading approximately 200 modules lazily) so keep an eye on our blogs. The old article about lazy loading will be updated but please do not use the approach there as it is now obsolete
Regards,
Nikolay Iliev
Progress Telerik
Hello Nikolay,
I tested a test project with the structure you presented, but it still does not work.
On the attached test project, when you first run it, it will work as expected. Then, if you follow these steps, it will break it again.
* On pages/pages.html, uncomment commented code and remove/comment the listview element which was previously in the page.
* Re-run "tns run android"
* Follow the same route again thorough Pages>Main>Inner>First
I commented out the RadListView in inner-one.html because it throws an error when I access that page and I can't figure out why. But even then, the TextField still pushes the page out of view with just a GridLayout.
Indeed it seems that the issue is not resolved even when using a lazily loaded router-outlet on root level.
Our developer's team will need additional time to investigate the issue and find a proper solution for this case.
Please keep track on the logged issue I have previously opened in the nativescript-angular repository for possible workarounds and additional information regarding this scenario.
Regards,
Nikolay Iliev
Progress Telerik