Lets say there is a login page with username\password TextFields and login Button. When the button is pressed a request is set to a server and ActivityIndicator is shown. Currently I put StackLayout on top of all other controls not to give the user a possibility to click on them while processing the request. But in some cases TextField stays focused and the user can type there.
I'm already using a component to wrap all TextFields to show validation errors:
@Component({
selector: "field",
template: "<grid-layout><ng-content></ng-content>...</grid-layout>"
})
export class FieldComponent {
@ContentChild(NgModel) private input: NgModel;
...
}
My question is can I set isEnabled property to false on TextField inside ng-content
from FieldComponent
having NgModel
or in some another way?
If it is impossible what is the best practices in this case to disable inputs when an app is busy?
There are a couple way you can do this;
You can use a
ngIf
or binding onisEnabled
to disable it based on a data bound value.You can create a simple routine that you call (my preferred method).
require("nativescript-dom");
function screenEnabled(isEnabled) {
runAgainstTagNames('TextEdit', function(e) { e.isEnabled = isEnabled; });
runAgainstTagNames('Button', function(e) { e.isEnabled = isEnabled; });
}
The nativescript-dom
plugin has the runAgainst*, or getElementBy* wrappers to talk to the native layer like you were talking to a html dom.
Full disclosure, I'm the author of nativescript-dom
, it is one of the plugins that I use in almost every app/demo I do.
Here is my solution for NativeScript+Angular:
- setControlInteractionState() is recursive.
- the TextField cursor is hidden (using native android API).
XML:
<GridLayout #mainGrid rows="*" columns="*">
<!-- Main page content here... -->
<GridLayout *ngIf="isBusy" rows="*" columns="*">
<GridLayout rows="*" columns="*" style="background-color: black; opacity: 0.35">
</GridLayout>
<ActivityIndicator width="60" height="60" busy="true">
</ActivityIndicator>
</GridLayout>
</GridLayout>
or
<GridLayout #mainGrid rows="*" columns="*">
<!-- Main page content here... -->
</GridLayout>
<GridLayout *ngIf="isBusy" rows="*" columns="*">
<GridLayout rows="*" columns="*" style="background-color: black; opacity: 0.35">
</GridLayout>
<ActivityIndicator width="60" height="60" busy="true">
</ActivityIndicator>
</GridLayout>
TypeScript:
import { Component, ViewChild, ElementRef } from "@angular/core";
import { View } from "ui/core/view";
import { LayoutBase } from "ui/layouts/layout-base";
import { isAndroid, isIOS } from "platform";
@Component({
templateUrl: "./SignIn.html"
})
export class SignInComponent {
@ViewChild("mainGrid")
MainGrid: ElementRef;
isBusy: boolean = false;
submit() : void {
try {
this.isBusy = true;
setControlInteractionState(<View>this.MainGrid.nativeElement, false);
//sign-in here...
}
finally {
this.isBusy = false;
setControlInteractionState(<View>this.MainGrid.nativeElement, true);
}
}
setControlInteractionState(view: View, isEnabled: boolean) : void {
view.isUserInteractionEnabled = isEnabled;
if (isAndroid) {
if (view.android instanceof android.widget.EditText) {
let control = <android.widget.EditText>view.android;
control.setCursorVisible(isEnabled);
}
}
if (view instanceof LayoutBase) {
let layoutBase = <LayoutBase>view;
for (let i = 0, length = layoutBase.getChildrenCount(); i < length; i++) {
let child = layoutBase.getChildAt(i);
setControlInteractionState(child, isEnabled);
}
}
}
}
NS 2.5.0
Expanding @KTCO's answer to get the size of the overlay exactly the same as the main grid:
import { Size, View } from "tns-core-modules/ui/core/view";
import { GridLayout } from "tns-core-modules/ui/layouts/grid-layout/grid-layout";
...
...
dialogSize: Size;
mainGrid: GridLayout;
...
submit() {
this.mainGrid = <GridLayout>this.MainGrid.nativeElement;
this.dialogSize = this.mainGrid.getActualSize();
.....
.....
<GridLayout *ngIf="isBusy" rows="auto" columns="auto">
<GridLayout rows="*" columns="*" [width]="dialogSize.width" [height]="dialogSize.height" style="background-color: black; opacity: 0.35">
</GridLayout>
<ActivityIndicator width="50" height="50" busy="true">
</ActivityIndicator>
</GridLayout>
I know this is a little old, but sometimes I just got to do things my way. If you want to accomplish this programmatically:
const excludedView = someViewToBeExcluded;
const enabler = (parentView:View, enable:boolean) => {
parentView.eachChildView(childView => {
if (childView != excludedView) {
enabler(childView, enable);
childView.isEnabled = enable;
}
return true;
});
};
enabler(page, false);
Note: This will not disable/enable the initial parentView
(ie. the page
in this example)
I found an easiest solution in Angular, so i am posting here for any future reference. First in app.component.html
file i added a Grid and ScrollView
like following:
<GridLayout>
<page-router-outlet></page-router-outlet>
<!-- hack to block UI -->
<ScrollView isUserInteractionEnabled="false" *ngIf="isLoading">
<ActivityIndicator busy="true"></ActivityIndicator>
</ScrollView>
</GridLayout>
Notice the page-router-outlet
which is inside the Grid. By default it will be place at row="0". The next thing is ScrollView
which has isUserInteractionEnabled set to false.
Now in your app.component.ts
file add a a variable called isLoading
and toggle it using some kind of events e.g RxJs Observable events
来源:https://stackoverflow.com/questions/40988124/nativescript-disable-all-controls-while-activityindicator-is-shown