NativeScript: Disable all controls while ActivityI

2020-04-17 02:56发布

问题:

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?

回答1:

There are a couple way you can do this;

  1. You can use a ngIf or binding on isEnabled to disable it based on a data bound value.

  2. 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.



回答2:

Here is my solution for NativeScript+Angular:

  1. setControlInteractionState() is recursive.
  2. 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



回答3:

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



回答4:

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>


回答5:

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)