Absolute/Fixed positioning without AbsoluteLayout


#1

Hi guys,

I’m creating a form validation error message that I want to display something like a tooltip floating below the invalid TextField.

I’m creating the element with Javascript and adding it to an AbsoluteLayout. I’m then positioning my error message within the AbsoluteLayout based on the position of the invalid TextField using getLocationInWindow()

The problem here is that the AbsoluteLayout overlays the entire page and prevents interaction with the TextField beneath (green overlay in screenshot).

Is there another mechanism I can use to position an element at an arbitrary XY coordinate in the page without using an AbsoluteLayout? or perhaps a way to allow tap events to pass through an AbsoluteLayout?

Here is my code for generating the error message:

function createValidationError(el) {

    // ## Get element size and position
    var elBounds = el.ios.layer.bounds;
    var elFrame = el.ios.frame;

    var elPosition = el.parent.getLocationInWindow();

    var elWidth = elBounds.size.width;
    var elHeight = elBounds.size.height;
    var elX = elFrame.origin.x;
    var elY = elFrame.origin.y;

    // ## Create wrapper element
    var wrapper = new absoluteLayoutModule.AbsoluteLayout();
    wrapper.className = 'validation-error-wrapper';
    wrapper.userInteractionEnabled = false;

    // ## Create validation error element
    var validationWrapper = new stackLayoutModule.StackLayout();
    absoluteLayoutModule.AbsoluteLayout.setLeft(validationWrapper, elPosition.x);
    absoluteLayoutModule.AbsoluteLayout.setTop(validationWrapper, elPosition.y + elHeight);

    var validationMessage = new labelModule.Label();
    validationMessage.className = 'validation-message';
    validationMessage.text = 'Please enter something.';

    var validationArrow = new stackLayoutModule.StackLayout();
    validationArrow.className = 'validation-arrow';
    validationArrow.horizontalAlignment = 'left';

    validationWrapper.addChild(validationArrow);
    validationWrapper.addChild(validationMessage);

    // ## Add validation error element to AbsoluteElement
    wrapper.addChild(validationWrapper);

    var frame = require('ui/frame');
    var page = frame.topmost().currentPage;
    var LayoutBase = require('ui/layouts/layout-base').LayoutBase;

    // ## Find LayoutBase and add AbsoluteLayout
    page._eachChildView(function (view) {
        if (view instanceof LayoutBase) {
            view.addChild(wrapper);
            return false;
        }
        return true;
    });

}

And the page:

<Page loaded="onLoaded" xmlns="http://schemas.nativescript.org/tns.xsd">

  <GridLayout id="pageWrap" rows="*">

    <GridLayout id="page-1" row="0" rows="*, auto, *" columns="30, *, 30">
      <GridLayout id="content-modal" row="1" col="1" rows="auto, auto, auto, auto, auto" class="content-modal">

          <Image row="0" src="~/img/pulse-triangle-icon.jpg" class="pulse-triangle-icon" />
          <Label row="1" text="LOGIN TO GET STARTED" textWrap="true" class="pulse-title text-primary text-cheltenham" />
          
          <StackLayout row="2" class="input-wrap">

            <GridLayout columns="18, *">
              <Image col="0" src="~/img/icon-envelope.png" />
              <TextField id="txtEmail" col="1" class="text-normal" text="{{ user.email, user.email }}" hint="Email address" keyboardType="email" autocapitalizationType="none" />
            </GridLayout>

            <GridLayout columns="18, *">
              <Image col="0" src="~/img/icon-key.png" />
              <TextField id="txtPassword" col="1" class="text-normal" text="{{ user.password, user.password }}" hint="Password" secure="true" />
            </GridLayout>
         
          </StackLayout>

          <GridLayout row="3" columns="10*, auto, 10*" class="btn-wrap" style="text-align: center">
            <Button col="1" text="LOGIN" class="btn btn-primary" tap="tapLogin" />
          </GridLayout>

        </GridLayout>
    </GridLayout>

  </GridLayout>

</Page>

$ tns info

┌──────────────────┬─────────────────┬────────────────┬──────────────────┐
│ Component        │ Current version │ Latest version │ Information      │
│ nativescript     │ 2.5.0           │ 2.5.2          │ Update available │
│ tns-core-modules │ 2.5.1           │ 2.5.1          │ Up to date       │
│ tns-android      │ 2.5.0           │ 2.5.0          │ Up to date       │
│ tns-ios          │ 2.5.0           │ 2.5.0          │ Up to date       │
└──────────────────┴─────────────────┴────────────────┴──────────────────┘

#2
  1. Is this only on Android or iOS or both?

  2. Have you tried to do (on Android):
    wrapper._nativeView.setClickable(false);

Nathanael A.


#3

Thanks @NathanaelA,

I’ve only tested on iOS but I will need it to work on both.

I’m away from my computer today so I can’t try setClickable(false) right now. Do you know if there is an iOS equivalent?


#4

Thinking about this further, what I actually want to be able to do is forget about the AbsoluteLayout altogether and just set the origin x/y of the validationWrapper element directly.

This, however, doesn’t seem to work:

validationWrapper.ios.frame = CGRectMake(100, 100, 200, 50); // test values

validationWrapper is always 100% width and heith and positioned at 0,0. Can we set the frame origin and size like this or am I going about it the wrong way?


#5

Not sure if possible I havent tried it but maybe try to make the “wrapper” smaller than the entire screen size so that it does not lock the user interaction on the entire screen but rather only at the “error massage label”. Here is how you can change the size of the AbsoluteLayout.


#6

Hi @Amiorkov,

Problem there is that I would have to then position the AbsoluteLayout within the page which I don’t seem to be able to do. This would also make the AbsoluteLayout redundant. If I could do this I wouldn’t need the AL at all.


#7

Hmm yeah that could be the case indeed. I think in order to be able to do
that via an AbsoluteLayout you may have to use only that container and
remove all of the other Stack or GridLayouts which will be a pain.


Setting frame of view with Javascript
#8

Solved this eventually: https://github.com/NativeScript/NativeScript/issues/3812