Hide Tab Buttons in NativeScript TabView - tabs

I'm using Nativescript with Typescript/Angular and, for both iOS and Android, I'd like to hide the navigation tab buttons completely without losing the swipe functionality between the tabs.
Said another way: I want the tab content, but not the buttons.
I'm open to other suggestions to gain the same functionality without the tab navigation menu.
The closest answer I could find was this:
NativeScript How to hide tab buttons from TabView
However, this answer didn't work. It caused the entire page to go white and none of the tab items appeared. It seemed as though the swipe functionality ceases to function as well.
Any ideas?
This is inside the html (not xml) file:
<TabView id="mainTab" selectedIndex="1">
<StackLayout *tabItem="{ title: 'Tab 1' }">
<app-page-one></app-page-one>
</StackLayout>
<StackLayout *tabItem="{ title: 'Tab 2' }">
<app-page-two></app-page-two>
</StackLayout>
<StackLayout *tabItem="{ title: 'Tab 3' }">
<app-page-three></app-page-three>
</StackLayout>
</TabView>

Old question but maybe someone else also hits this so giving more updated answer.
Nativescript v6 introduced Tabs (and BottomNavigation) with purpose to replace TabView: https://nativescript.org/blog/tabs-and-bottomnavigation-nativescripts-two-new-components/
So solution with Tabs is simply to remove the TabStrip portion, e.g.
<Tabs>
<TabContentItem>
<GridLayout>
<Label text="Content for Tab 1" />
</GridLayout>
</TabContentItem>
<TabContentItem>
<GridLayout>
<Label text="Content for Tab 2" />
</GridLayout>
</TabContentItem>
</Tabs>

The best way todo this is to do it programatically. Take alook at this issue here at https://github.com/NativeScript/nativescript-angular/issues/621.
Simply create tabs programmatically and then you can control them. You can't remove tabs from hierarchy once they are added to the tree from the UI.

I had the same problem and found a solution that works on android at least, maybe somebody can provide an iOS solution. You need to annotate the TabView so you can access the underlying Android component like i did with #mainTabView
<TabView #mainTabView androidTabsPosition="bottom">
<GridLayout *tabItem="{iconSource: 'res://ur_news', title: 'Home'}">
<Label text="Tab 1"></Label>
</GridLayout>
[...]
</TabView>
Then, in the component you can reference this element, access the internal tabView and use android native calls to hide it.
import { Component, ElementRef } from '#angular/core';
[...]
// angular will inject a reference to the native implementation here, we can use it
#ViewChild('mainTabView') containerRef: ElementRef;
[...]
protected handleAndroidFullscreenMode(isFullscreen: boolean): void {
// wait a little bit until calling this - if it is empty it might not yet be there
// fetch the underlying nativeElement
const tabView = this.containerRef.nativeElement as TabView;
if(!tabView) {
console.log("native element not yet initialized");
return;
}
// the tabLayout contains the buttons we want to hide
const tabLayout = tabView.nativeView.tabLayout;
if(!tabLayout) {
console.log("No tab layout");
return;
}
// use native android methods to hide them
if(isFullscreen) {
tabLayout.setVisibility(android.view.View.GONE);
} else {
tabLayout.setVisibility(android.view.View.VISIBLE);
}
}

Related

Is it possible to use [maxSelectedLabels] property in an ngif condition?

I'm using Prime NG Multiselect component and I want to show selectedItemsLabel="{0} Selected" when there are more than 3 selected checkboxes, but if all of the checkboxes are selected, then selectedItemsLabel="All" should be shown in the placeholder.
I'm new to angular and I been following documentation of this MultiSelect component, yet this doesn't show the options to able to implement multiple conditions of properties, and I was wondering if it's even possible.
Example of how It might be
<ng-template pTemplate="filter" let-value let-filter="filterCallback">
<p-multiSelect
[ngModel]="value"
[options]="routeOptions"
placeholder="Any"
(onChange)="filter($event.value)"
optionLabel="name"
selectedItemsLabel="{0} selected"
[maxSelectedLabels]="3"
>
<ng-template let-option pTemplate="item">
<div>
<span class="p-ml-1">{{ option.name }}</span>
</div>
<div *ngIf="[maxSelectedLabels="routeOptions.length - 1"] Then selectedItemsLabel="All"></div>
</ng-template>
</p-multiSelect>
</ng-template>
Yes, you can. First give the component a ref with # like this:
<p-multiSelect
#myMultiSelect
[ngModel]="value"
[options]="routeOptions"
placeholder="Any"
(onChange)="filter($event.value)"
optionLabel="name"
selectedItemsLabel="{0} selected"
[maxSelectedLabels]="3"
>
.......
Then you have access to it:
<div *ngIf="myMultiSelect.maxSelectedLabels === routeOptions.length - 1">Im visible</div>
If the option of maxSelectedLables is the length - 1 of routeOptions then the div is visible. That is how ngIf works
BUT
Thats not what you want. You wanna set the selectedItemsLabel property. And you have it not understand correctly. You set the maxSelectedLables to 3 as example AND set the selectedItemsLabel directly, too! The text of the selectedItemsLabel will be only shown if needed (controlled by the component).
<h5>Basic</h5>
<p-multiSelect #meins [options]="cities" [(ngModel)]="selectedCities" defaultLabel="Select a City" optionLabel="name"
[maxSelectedLabels]="3" selectedItemsLabel="{0} items selected">
</p-multiSelect>
Look here the Stackblitz!
The documentation of ng-prime will helps, too and say:
selectedItemsLabel: Label to display after exceeding max selected labels e.g. ({0} items selected), defaults "ellipsis" keyword to indicate a text-overflow.
UPDATE 18.02.2023
You wanna show "ALL" only if all items selected. So add the onChange event and bind the selectedItemsLabel. Why binding? It has some problems with a condition in it. So we make it inside the code.
HTML
<p-multiSelect [options]="cities" [(ngModel)]="selectedCities" defaultLabel="Select a City" optionLabel="name"
[maxSelectedLabels]="2" [selectedItemsLabel]="bindTest" (onChange)="onChange()">
</p-multiSelect>
Inside the code do the follow with onChange:
Code
onChange() {
if (this.selectedCities.length === this.cities.length) {
this.bindTest = "ALL";
this.changeRef.detectChanges();
}
else {
this.bindTest = "{0} items selected";
this.changeRef.detectChanges();
}
}
Now it works how you wish. One important thing: We use changeRef.detectChanges(); Without this the components selected text will not changing directly. Import it in the components constructor:
constructor(
private countryService: CountryService,
private primengConfig: PrimeNGConfig,
private changeRef: ChangeDetectorRef
) {
.....
I made a Stackblitz of the problem: https://stackblitz.com/edit/primeng-tablefilter-demo-ipt7y1?file=src%2Fapp%2Fapp.component.html,src%2Fapp%2Fapp.component.ts
(Expand the page to the left to view the column filter in the stackblitz)
If you notice, the clear button doesn't clear the selected textbox anymore. After some testing it seems the [(ngModel)] breaks it, I think it got to do something with two-way binding? It is not shown in the stackblitz, but if you include
(onChange)="filter($event.value)"
the clear button still clears the filter from the table, but not in the selected textbox.
I found out that there is this property
[showClear]="true"
That adds an X at the end of the textbox that clears it out. Sadly, the styling/positioning is not what I need.
What could be the ways to fix the clear button ? Add a ts function to clear out the selected values? If so, how to bind it to the clear button because it is generated from
<p-columnFilter
display="menu"
menu property and I had no luck to find the way to try add/change functionality to that button.

Angular. Getting wrong data from ngb-panel

I use ngb-accordion in my app. I am trying to get data from every panel but when the first panel is opened click from the second panel returns me wrong data.
Result
I think the problem is the event which raises when input file changes.
Stackblitz Link
I will be glad if someone give me a hint for solving this problem.
There are few things to note in your code.
Your *ngFor is at ngb-accordion which is creating a new accordion for every loop, instead of creating multiple panel within one accordion.
Fix: <ngb-panel *ngFor="let data of datalist; let i = index">
You are using the same label for all three panels, because of which your first panel is opening every time, regardless of which panel you are clicking.
Fix: <label [for]="'image-input-' + i"> and <input ... [id]="'image-input-' + i"
The modal that opens after image selection has no knowledge of which panel it's getting triggered from. So, you have to use your (change)="onFileChange($event, data)" event/function to keep track of selected panel/corresponding data.
Then you can pass that selection from your modal to your processFile(...)
Fix:
export class AppComponent {
...
selectedData: Data;
...
...
onFileChange(event: any, data): void {
...
this.selectedData = data;
}
}
html:
...
<input ... (change)="onFileChange($event, data)>
...
...
<button
...
(click)="processFile(imageInput, selectedData)"
> Done
</button>
Stackblitz Demo

What is the HTML <dialog> tag used for and when to use it?

The way I've understood it, the tag is used to open and close content like a popup alert. What I fail to understand is what advantages the tag has compared to just using a "div" and styling it with css and adding functionality to it with js. It also seems counter intuitive to manipulate the "open" property in order to show/hide the content instead of using display:none/block; with css.
I also don't understand exactly which scenarios would be considered a dialog box. Is a form login box a dialogbox? What about a popup telling you to disable adblock? Are all popups that can be hidden considered dialog boxes?
The traditional, hacky way to create a dialog, via designing a div via CSS only seems to be intuitive for you because you are used to it. However, you need to implement every functionality related to it, such as:
opening it
closing it
Also, in the future, this will be enhanced by standard functionalities, so, while it's not urgent for already existent code, but when you write code, especially when you start a project, it makes sense to start using it. Let's see an example from [Mozilla's page][1]:
var updateButton = document.getElementById('updateDetails');
var favDialog = document.getElementById('favDialog');
var outputBox = document.querySelector('output');
var selectEl = document.querySelector('select');
var confirmBtn = document.getElementById('confirmBtn');
// "Update details" button opens the <dialog> modally
updateButton.addEventListener('click', function onOpen() {
if (typeof favDialog.showModal === "function") {
favDialog.showModal();
} else {
alert("The <dialog> API is not supported by this browser");
}
});
// "Favorite animal" input sets the value of the submit button
selectEl.addEventListener('change', function onSelect(e) {
confirmBtn.value = selectEl.value;
});
// "Confirm" button of form triggers "close" on dialog because of [method="dialog"]
favDialog.addEventListener('close', function onClose() {
outputBox.value = favDialog.returnValue + " button clicked - " + (new Date()).toString();
});
<!-- Simple pop-up dialog box containing a form -->
<dialog id="favDialog">
<form method="dialog">
<p><label>Favorite animal:
<select>
<option></option>
<option>Brine shrimp</option>
<option>Red panda</option>
<option>Spider monkey</option>
</select>
</label></p>
<menu>
<button value="cancel">Cancel</button>
<button id="confirmBtn" value="default">Confirm</button>
</menu>
</form>
</dialog>
<menu>
<button id="updateDetails">Update details</button>
</menu>
<output aria-live="polite"></output>
However, at the time of this writing (February the 3rd, 2022), this is not supported in all browsers, so it is perfectly feasible to avoid using it for now, until it will become supported everywhere.
[1]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog

Accessibility with complex custom components

Accessibility guidelines were invented before components were released, so they always say that a label is used to identify a form control like <input> or <textarea>, etc. What happens when I have a complex Angular / React / ... component that acts like a form control?
Imagine a <custom-select> that renders an input and adds items to a list. The resulting html looks like:
<custom-select ....>
<input ...>
</custom-select>
When I type something in the input and I press enter, it adds that entry to the list and renders the input again, something like:
<custom-select ....>
<span>What I typed</span>
<input ...>
</custom-select>
Of course, if I type something else in the input and I press enter, it gets added to the list:
<custom-select ....>
<span>What I typed</span>
<span>Something else</span>
<input ...>
</custom-select>
If we want to use this custom component in a form, we would like to put a label to it like any other form item, p.e:
<label for="foo">Foo</label>
<input id="foo" type="text">
<label for="select">Select a country</label>
<custom-select id="select"></custom-select>
Is this even valid a11y? Wave tool will complain of an orphan label while axe says nothing. So, can we use a plain old label to tag a custom component for accessibility purposes? We need a label to be put there for consistency but needs to be accessible.
In case I can do this, that custom-select is also rendering an input. That input needs its own label or aria-label, right?
Yes the input will need to be labeled.
Is there any reason for the component to not manage this? Accept the labeling text and then render the correct accessible HTML for the label and input pair?
So in React:
<CustomSelect labelText="Enter your destination" />
with the component doing:
const id = generatedUniqueId() // This will need to be memoized in the real implementation to avoid creating new id's with every render.
...
<>
<label for={id}>{labelText}</label>
<input id={id} />
</>
Atleast in angular: You can preserve a11y like the following:
// Custom Input HTML (Using Angular Material for eg);
// You can import the label inside the custom component and bind it to the
input field so that you can always have a new ID to every custom input
<mat-form-field [attr.aria-labelledby]="customAriaLabelledByIDs">
<mat-label *ngIf="label" [for]="customId">{{ label }}</mat-label>
<input matInput [id]="customId" />
</mat-form-field>
// In your component.ts file,
#Input() customId: string;
#Input() customAriaLabelledByIDs: string[];
combinedAriaLabelledByIDs: string;
ngOnInit() {
if (this.customAriaLabelledByIDs) {
this.combinedAriaLabelledByIDs =
this.customAriaLabelledByIDs.length === 1
? this.customAriaLabelledByIDs[0]
: this.customAriaLabelledByIDs.join(' ');
}
}
/// Wherever you use, now you will have something like this:
<your-custom-selector
[customAriaLabelledByIDs]="['id-1', 'id-2', 'id-3']"
[customId]="first-name-ID"
></your-custom-selector>
<your-custom-selector
[customAriaLabelledByIDs]="['id-4', 'id-5', 'id-6']"
[customId]="second-name-ID"
></your-custom-selector>
etc.,
You can add multiple aria-attributes to the #Input() and use it from the custom component like, aria-label, role, aria-expanded, etc...
Let me know if you need any more explanation on any of the things i mentioned above. Will be happy to help!!

Parent/child click relationships in AngularJS directives

I have a custom directive placed on a Kendo UI treeview widget.
It seems to be working fine side-by-side, except that I'm trying to simply display the custom icons next to the tree node which is clicked on (see sample image below).
So my directive is data-toggle-me, placed next to the Kendo k-template directive as follows :
<div class="reports-tree" kendo-tree-view="nav.treeview"
k-options="nav.treeOptions"
k-data-source="nav.reportsTreeDataSource"
k-on-change="nav.onTreeSelect(dataItem)" >
<span class="tree-node" k-template data-toggle-tree-icons>{{dataItem.text}}</span>
</div>
and the directive code here inserts some custom icons next to the tree node when a user clicks on that tree node :
.directive('toggleMe', function ($compile) {
// Kendo treeview, use the k-template directive to embed a span.
// Icons appear on Click event.
return {
restrict: 'AE',
transclude: true,
template: '<span ng-show="nav.displayIcons" id="myIcons" class="reptIcons" style="display:none;width:50px;align:right;">' +
' <a title="add new folder" ng-click="nav.addAfter(nav.selectedItem)"><i class="fa fa-folder-open"></i></a> ' +
'<a title="add report here" ng-click="nav.addBelow(nav.selectedItem)"><i class="fa fa-plus"></i></a> ' +
'<a title="remove" ng-click="nav.remove(nav.selectedItem)"><i class="fa fa-remove"></i></a> ' +
'<a title="rename" onclick="showRename(this);"><i class="fa fa-pencil"></i></a>' +
'</span>',
link: function (scope, elem, attrs) {
var icons = elem.find("#myIcons");
elem.on('click', function (e) {
$('.reptIcons').css('display', 'none');
icons.css("display", "inline");
icons.css("margin-left", "5px");
});
}
}
})
My biggest problem at this point is getting the icons to appear on the treenode which is clicked on. Then once the user clicks on a different node, the icons will only render again on the newly-clicked node.
This fiddle represents a partially-working example but the icons are appearing on every single treenode - click tree item to show icons
**** UPDATED TREE IMAGE - All child nodes now show icons (not what I want) ****
I'm not sure to understand your issue, you should try to reduce the code to the minimum and have a snippet/jsfiddle that works.
If all you want is not trigger click events when $scope.disableParentClick is set to true, simply add
elem.on('click', function (e) {
// Do not execute click event if disabled
if (!$scope.disableParentClick) { return; }
...
});
Now that seems all not very angular friendly to me. You should externalize your HTML in either the template or templateUrl of your directive, potentially adding to it a ng-if="displayTemplate" which would only display the node when a click would set $scope.displayTemplate = true;
Also, instead of listening for click events this way, you should use the ng-click directive. Everything is doable with directives. I can give more information when you better understand your problem: I suspect you are not approaching it the right way.
UPDATE: if all you want is display the icons list of the clicked element, you could do it way easier. You actually don't need the toggle-me directive, but even if you keep it you can solve all your troubles the angular-way, which is by using ng-click, ng-repeat, etc. Please have a look at the following jsFiffle to see one way of doing that. There are many other ways, but really try using ng-click to avoid troubles:
http://jsfiddle.net/kau9jnoe/
Events in the DOM are always bubbling up. That is a click on a link would trigger an onclick handler on every element up the hierarchy, e.g. also the body element. After all the click happened within body.
The same is true for your directive. Any click within your element triggers its event handler. To circumvent this either attach the event handler somewhere else or ignore clicks from the links.
The event object has a target property that tells you what element initiated the event. So you could do something like this:
elem.on('click', function (e) {
if (e.target.nodeName.toLowerCase() == 'a') return; //ignore click on links