Adding horizontal ScrollView to code-generated GridLayout - nativescript

I have this sample code that works as I want:
<ScrollView orientation="horizontal">
<GridLayout columns="*,*,*,*,*,*" >
<Label class="gridlabel" col="0" text="Monday" />
<Label class="gridlabel" col="1" text="Tuesday" />
<Label class="gridlabel" col="2" text="Wednesday" />
<Label class="gridlabel" col="3" text="Thursday" />
<Label class="gridlabel" col="4" text="Friday" />
<Label class="gridlabel" col="5" text="Saturday" />
</GridLayout>
</ScrollView>
That is, the labels within the GridLayout scroll horizontally.
I have a component that generates a GridLayout, and now I need to wrap that in a horizontal ScrollView.
That is, I for each label:
let label = new Label();
// Add tap event handler for each tab
label.on("tap", function () {
onTabTap(label, "tabTap");
}.bind(label));
label.id = key;
label.text = key;
label.class = "gridtab";
this.addColumn(new ItemSpec(1, GridUnitType.STAR));
GridLayout.setColumn(label, i);
GridLayout.setRow(label, 0);
this.addChild(label);
But when I try to add the ScrollView, I get errors. If I try to add the labels to the ScrollView, such as scrollView.addChild(label) (where scrollView is an instance of ScrollView), I get "scrollView.addChild is not a function". (See this similar SO post). If, as suggested in the mentioned post, I use scrollView.content = this; then I get the error, Error: View already has a parent.
So, the question is, from code, how do I replicate the hierarchy from my sample xml? That is, how can I wrap the generated GridLayout in a horizontal ScrollView?
Edit 7/17/2020
Upon reflection, I don't think this can work given my component's current design. That is, it subclasses GridLayout, and I want the generated GridLayout to be wrapped by a ScrollView, but that would be external to the content generated by the component, yes? It almost seems I'd need to subclasss ScrollView, and then generate the GridLayout within.

So, I was ultimately able to resolve this by subclassing StackLayout, then within the StackLayout adding a ScrollView, and within the ScrollView adding the GridLayout. The "magic" is:
scroll.content = grid;
this.addChild(scroll);
Where scroll is the ScrollView instance, and grid is the GridLayout instance.
Then, after spending a day on this I found I didn't actually need horizontal scrolling after all, but at least I know what to do should the need arise.

Related

Nativescript – center horizontal text inside textView, dynamic text and multiline

I've got a nativescript app. I have a situation were I need to display text dynamically, so I don't know how much text and how much lines it will be.
The text need to wrap over multi lines and has to be aligned in center horizontally (not vertically, simply same distance to left and right) always.
Therefore I guess <Label> isn't the right element, because it is not for multiline (if I got this right?!).
So I choose <TextView>, but here the styles text-align: center got ignored.
So in documentation I found constructor textAlignment https://docs.nativescript.org/api-reference/modules/_ui_text_base_#textalignment, but I don't get it to work.
Doesn't work:
<TextView text="{{ taskString }}"
horizontalAlignment="center"
editable="false"
></TextView>
Doesn't work:
<TextView text="{{ taskString }}"
textAlignment="center"
editable="false"
></TextView>
Please let me know, what obvious I didn't get here. Thx.
Upate:
Label with textWrap="true" normally works just fine. But I have a "complex" <ContentView> <FlexboxLayout> combination, that seems to cause the problem with the height of Label that doesn't get updated.
Solution in my case:
Just don't use a <FlexboxLayout> <FlexboxLayout> ... </FlexboxLayout> </FlexboxLayout>solution. That won't calculate the height of Label dynamically.
Nativescript Playground:
https://play.nativescript.org/?template=play-ng&id=SnNmkQ
Label can be multiline if you enable textWrap
XML
<Label text="{{ taskString }}" textWrap="true" ...
Or
CSS
Label {
white-space: normal;
}

Nativescript Stacklayout does not layout properly?

I have this simple layout markup :
<StackLayout orientation="horizontal" #myStack>
<StackLayout width="300" backgroundColor="red">
</StackLayout>
<StackLayout width="300" backgroundColor="green">
</StackLayout>
</StackLayout>
As you can see , both are same width of 300:
Let's see this :
Notice that green is also 300 but we only see a part of it since 300+300 > screen size.
Ok so let's try to animate #myStack (!!) to the left , in order to see the left green :
ngOnInit(): void {
setTimeout(() => {
this.myStack.nativeElement.animate({
translate: { x: -300, y: 0 },
duration: 2000,
curve: enums.AnimationCurve.easeIn
});
},1000)
}
Question:
What is this white area on the right ? there should be also a green section there
Basically this is the situation :
So i'm expecting the green from the right to be scrolled to the left , i'm basically trying to move #myStack left.
How can I make the green area slide from the right ?
PLAYGROUND
Copy/paste this answer so that the community can see the provided solutions:
#RoyiNamir this is expected behavior.
What is happening is that by default the root layout will have an effective width (and height) as the screen size.
You need to explicitly create the wider container if you want to have an effective width larger than the screen width.
There are several approaches on how to achieve that.
- use container with fixed width - demo Playground here
<GridLayout rows="auto, *" columns="600" backgroundColor="lightgray">
<Button row="0" text="animate" (tap)="animate()"></Button>
<StackLayout row="1" orientation="horizontal" #myStack>
<StackLayout width="300" backgroundColor="red">
<Label text="Label"></Label>
</StackLayout>
<StackLayout width="300" backgroundColor="green">
<Label text="Label"></Label>
</StackLayout>
</StackLayout>
</GridLayout>
Note that our container GridLayout has columns set to 600.
Another option is instead of creating fixed size container to use ScrollView (which will measure its children so the children should have predefined size - in your case width="300") - demo Playground here
In the example above the container element is not needed (used just to create the animate Button).
The ScrollView will measure its children (300 + 300 = 600 width) and will take the space needed.

How to show a button if there are no items in a ListView?

I have the following structure:
<ScrollView tkMainContent>
<ListView [items]="students$ | async" class="list-group" *ngIf="students$">
<ng-template let-student="item">
<StackLayout>Student details go here</StackLayout>
I'm not able to show a button inside the ScrollView when there is no student in my list.
How can I still show the button?
Note: I'm testing on a real iOS device.
<FlexboxLayout flexDirection="column">
<GridLayout class="page-content" id="placeholderLayout" visibility="{{ hasContent ? 'collapse' : 'visible' }}">
<Label class="page-icon fa" text=""></Label>
<Label class="page-placeholder" style="white-space: normal" text="Click the camera button to add image"></Label>
</GridLayout>
<ScrollView>
<-- List View Here -->
</ScrollView>
</FlexboxLayout>
I use something like this on NS Core, to show placeholder content. The way to set visibility might be different in angular, but a similar markup should work for you.
In the component.ts, you should take care to evaluate if there is content to show in list view, if there are, then set hasContent to true, and false otherwise.
Hope that helps :) let me know if you face any trouble while implementing this.

In NativeScript, how can I make a label automatically resize to fit content?

I have two buttons with a label in between. When a button is tapped, the label text changes, but it appears to have a static width set to the width of the initial text. When the text gets longer, part of it is replaced with an ellipses.
The code is pretty simple.
<StackLayout orientation="horizontal">
<Button text="<" tap="onPreviousLevel"></Button>
<Label id="title" text="{{ name }}"></Label>
<Button text=">" tap="onNextLevel"></Button>
</StackLayout>
Setting the horizontal alignment for labels seems to make them responsive (change based on content).
In CSS this would be
#title {
horizontal-align: center;
}
Try this:
<Label id="title" text="{{ name }}" textwrap="true"></Label>
Another way, in iOS:
var myLabel = page.getViewById("title");
myLabel.ios.numberOfLines = 2;
Not sure if it would be appropriate in this case, but in mine, ridiculously adding a "width:auto;" style override to the label pretty much did the trick, so far preventing the string to truncate and get the ellipsis at the end.
For example, a given TextField input (and probably many other components) with a "width:xx%;" sitting next the label will stretch or compress accordingly, depending on the label's string length, and the component's remaining space in a given container such as a horizontally oriented StackLayout.
For NativeScript Angular:
<Button text="Some label" horizontalAlignment="center" />
The Button will then only take the width that it needs, like fit-content would usually do. I have no technical explanation for that - it is just some NativeScript magic (if you could call it so..)
Source

How to add an activity-indicator on a page level?

I would like to add an activity-indicator widget in my login page but I would like it to cover the whole screen, so I can prevent double click on the Login button.
Any idea, thanks!
If you wrap everything in a GridLayout, add a StackLayout as the last item in the row you want to cover. The StackLayout by default will cover the whole screen. Then you can show/hide via data. For example:
<GridLayout>
<StackLayout>
// All your page content goes here!
</StackLayout>
<StackLayout class="dimmer" visibility="{{showLoading ? 'visible' : 'collapsed'}}"/>
<GridLayout rows="*" visibility="{{showLoading ? 'visible' : 'collapsed'}}">
<ActivityIndicator busy="true" />
</GridLayout>
</GridLayout>
I have a "dimmer" StackLayout that I animate to be semi transparent black, then the Activity Indicator sits on top.
not sure what layout you have i will only put example(somehow simplified) from my project
Inside page u can put something like this, both StackLayout and ActivityIndicator are inside GridLayout which takes whole size of page
<GridLayout rows="*" columns="*">
<StackLayout visibility="{{ showLogin ? 'visible' : 'collapse'}}" row="0" column="0">
<!--Login form, as you have defined-->
</StackLayout>
<!--Indicator on whole page, colSpan and rowSpan force ActivityIndicator to takes whole page-->
<ActivityIndicator visibility="{{ !showLogin ? 'visible' : 'collapse'}}" busy="{{ !showLogin }}" rowSpan="1" colSpan="1" row="0" column="0" />
</GridLayout>
And inside javascript code
/*somehow add showLogin property to bindingContext*/
page.bindingContext.set("showLogin",false) //false for show ActivityIndicator
/*or*/
page.bindingContext.set("showLogin",true) //true for show form
But best would be to put to already defined Observable which you should have assigned to bindingContext
So based on showLogin property u will get visible either ActivityIndicator(on whole page) or form
Not sure if i forgot something but if something, write comment :)
The activity indicator on its own won’t prevent dual submissions of your forms. In addition to displaying an ActivityIndicator, you should also set the isEnabled flag on your Button UI components to false during the submission. For example:
<!-- template -->
<Button [isEnabled]="!isAuthenticating" (tap)="submit()"></Button>
// JavaScript/TypeScript
export class LoginComponent {
isAuthenticating = false;
submit() {
this.isAuthenticating = true;
doTheActualLogin()
.then(() => {
this.isAuthenticating = false;
});
}
}
You can find a complete implementation of a login that prevents dual submissions and uses an ActivityIndicator in the NativeScript Groceries sample. Take a look at how the isAuthenticating flag is used in this login folder for the specific implementation.

Resources