I am using mat dialog to create a dialog box with 100's of input boxes. So it will take few seconds to load. I want to show a busy indicator when the dialog is loading.
Very simple code. On a button click I set a boolean to true and call dialog.open(). Then set it to false on dialogRef.afterOpened() event.
But the boolean doesnt get set to true until the dialog.open() event is completed. I can't figure out why.
StackBlitz here
https://stackblitz.com/edit/angular-d6nfhr
Enter value of say, 1000;
I am expecting the text 'Dialog opening...' (near to Add button) to appear soon after I click Add button. But it flashes for a second after the dialog is ready.
The solution does not answer the question as per the intended question but will act as a workaround solution.
Solution
Instead of showing the loading icon when loading any number of data, as per UX, it better for the user to show a limited number of input boxes and add a button that will add more input text field in a dialog box.
dialog.component.ts
#Component({
selector: 'dialog-overview-example-dialog',
templateUrl: 'dialog-overview-example-dialog.html',
})
export class DialogOverviewExampleDialog implements OnInit {
animals: any[] = [];
getTotalCountVal = null;
start: number = 0;
end: number = 20;
constructor(
public dialogRef: MatDialogRef<DialogOverviewExampleDialog>,
#Inject(MAT_DIALOG_DATA) public data: DialogData) { }
ngOnInit() {
this.getTotalCountVal = this.data.totalCount;
if (this.getTotalCountVal) {
for (let i = 0; i < this.data.totalCount; i++) {
this.animals.push('');
}
}
}
loadMore() {
if(this.end < this.getTotalCountVal) {
this.end += 20;
}
}
onNoClick(): void {
this.dialogRef.close();
}
}
dialog.component.html
<h1 mat-dialog-title>Total - {{data.totalCount}}</h1>
<div mat-dialog-content>
<p>Add favorite animals</p>
<ng-container *ngFor="let animal of animals | slice: start: end; let index=index">
<mat-form-field>
<input matInput [(ngModel)]="animal" name ="animal">
</mat-form-field>
</ng-container>
<button *ngIf="end < animals.length" mat-raised-button color="primary" (click)="loadMore()">Add more animals</button>
</div>
<div mat-dialog-actions>
<button mat-button (click)="onNoClick()">No Thanks</button>
<button mat-button [mat-dialog-close]="animals" cdkFocusInitial>Ok</button>
</div>
stackblitz working demo
Related
I have a react application and I'm using #testing-library/jest-dom for unit testing and antd 3.x library for UI design. In one of my UI screen there is a table and a button where the button only enables when one of the row in table is checked. So I wanted to do a unittest for this. So below is my src code,
import {Modal, Button, Form, Input, DatePicker, Table, Card, Tooltip} from 'antd';
...
...
return (
<>
<Form>
...
<Card>
<Table
data-testid={'lift-hold-grid'}
className="holdListResultsTable"
rowSelection={rowSelection}
columns={columns}
dataSource={dataSourcee}
components={{
body: {
row: showRestorationTooltip
}
}}
/>
</Card>
...
<div style = {...}>
<Button data-testid={'lift-hold-button'} disabled={...} onClick={...}>
Lift Hold
</Button>
</div>
<Form>
</>
)
and below is the unittest
import { render } from "#testing-library/react";
import { Provider } from "react-redux";
import thunk from "redux-thunk";
function renderWithReduxAndThunk(ui, initialState) {
const createdStore = createStore(rootReducer, initialState, applyMiddleware(thunk));
return {
...render(<Provider store={createdStore}>{ui}</Provider>),
createdStore,
}
}
const changedState = {
...
}
it('should enable the lift hold button after checkbox selection and note input', () => {
const { container, getByTestId } = renderWithReduxAndThunk(<LegalHoldLiftScreen />, changedState);
const checkBox = container.querySelector('.ant-checkbox-input');
fireEvent.click(checkBox);
const downloadButton = getByTestId('lift-hold-button');
expect(downloadButton).toBeTruthy();
expect(downloadButton).not.toBeDisabled();
});
but this fails with below message
● should enable the lift hold button after checkbox selection and note input
Unable to fire a "click" event - please provide a DOM element.
what am I missing here??
I'm using element.scrollIntoView() in my angular app.
I'm getting the desired element to come into view when my click function works.
my TS file:
elem1;
elem2;
ngOnInit() {
this.elem1 = document.getElementById('elem1');
this.elem2 = document.getElementById('elem2')
}
handleElemScroll(elem) {
// elem.scrollTop = 1000;
elem.scrollIntoView({behavior: 'smooth', block: 'end'});
}
And in my template, I am using mat-nav-list, and on click, passing the id of the element and using elem1.scrollIntoView() or elem2.scrollIntoView() to bring the desired element into view (for now bring it into view, but ideally the element should be at the top).
<div class="new-side-nav">
<mat-nav-list>
<a mat-list-item>
<span (click)="handleElemScroll(elem1)">Go to elem1</span>
</a>
<a mat-list-item>
<span (click)="handleElemScroll(elem2)">Go to elem2</span>
</a>
</mat-nav-list>
</div>
When the click function works, I am able to see both elements come into view on click (scrollIntoView works). But the click behavior is unpredictable. Sometimes the click works and the handleElemScroll() function runs, others time it does nothing.
Any clue as to why this occurs?
Ok, as i told you before look at this code (Angular 6 in my case, but it must work from 2):
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'eas-requests',
templateUrl: './requests.component.html',
styleUrls: ['./requests.component.css']
})
export class RequestsComponent implements OnInit {
// No need to declare
el1:HTMLElement;
el2:HTMLElement;
constructor() { }
ngOnInit() {
//No need to declare
this.el1 = document.getElementById('el1');
this.el2 = document.getElementById('el2');
}
// Just declare this method and receive html element.
clickEvent(e:HTMLElement){
e.scrollIntoView({behavior: 'smooth', block: 'end'});
}
}
And html:
<div id="el1" (click)="clickEvent($event.target)">Element 1</div>
<div id="el2" (click)="clickEvent($event.target)">Element 2</div>
In this action you pass click event. So in function you receive HTMLElement what you click. In my case all work fine as expected on any element where i use click event.
I am trying to make a self nested component that uses Angular Material mat-menu. I have a flyoutcomponent that is a wrapper for flyout-menu-item component, that will have a button as a matMenuTrigger for the nested component that will appear as many levels as the FeatureInput.FeatureChoices dictates. FeatureInput is an object that has FeatureChoices that may or may not contain other featurechoices etc N levels deep. Below code does not compile but it should demonstrate what I am doing. Basically I have flyout menu component as a input to a form and I am trying to load a stored answer on a form rather than select new, which I can do easily using the nested component. The desired behavior is that if the user clicks top matMenuTrigger button to open the top menu that it would expand all child menus to the menu item that matches with the FeatureInput.FeatureValue and sets the menu item _highlighted to true. I am using the menuOpen input parameter and ngChanges successfully to find the match(with I a setTimeout which cannot be right). Basically when I console.log this.trigger it is undefined. Ideally in the ngOnChange to the openMenu I would go through all menus and call openMenu on all the triggers but I cannot get access to the matMenuTrigger with ViewChild as the docs say. I get undefined. *-( All help welcome please and thanks.
Here is flyout template component.
<div>
<buttonmat-button [matMenuTriggerFor]="menu.childMenu"
(onMenuOpen)="onMenuOpen()"
(onMenuClose)="onMenuClose()">
<span [innerHTML]="featureInput.Text"></span>
</button>
<app-flyout-menu-item #menu
[featureChoicesObject]="featureInput.FeatureChoices"></app-flyout-menu-item>
</div>
And here is its .ts
import { Component, OnInit, Input, ViewChild } from '#angular/core';
import { MatMenuTrigger } from '#angular/material';
#Component({
selector: 'app-flyout',
templateUrl: './flyout.component.html',
styleUrls: ['./flyout.component.scss']
})
export class FlyoutComponent implements OnInit {
#Input() featureInput: FeatureInput
constructor() { }
ngOnInit() {
}
onMenuOpen() {
this.menuOpen = true;
}
onMenuClose() {
this.menuOpen = false;
}
}
And here is flyout-menu-item template
<mat-menu #childMenu="matMenu" [overlapTrigger]="false">
<span *ngFor="let featureChoice of featureChoices">
<span>
<button mat-menu-item [matMenuTriggerFor]="menu.childMenu">
<span [innerHTML]="featureChoice.Text"></span>
</button>
<app-flyout-menu-item #menu
[menuOpen]="menuOpen"
[featureInput]="featureInput"
[featureChoicesObject]="featureChoice.FeatureChoices"
(onOptionSelected)="someService.SomeMethod($event)"></app-flyout-menu-item>
</span>
<span *ngIf="!featureChoice.FeatureChoices">
<button mat-menu-item (click)="selectOption(featureChoice.ID)" [innerHTML]="featureChoice.Text" value="{{featureChoice.ID}}"></button>
</span>
</span>
</mat-menu>
And here is its .ts
import { Component, OnInit, Input, Output, ViewChild, EventEmitter, OnChanges, SimpleChanges } from '#angular/core';
import { MatMenuTrigger } from '#angular/material';
import { FeatureChoice } from 'app/model/feature-choice';
import { FeatureInput } from 'app/model/feature-input';
#Component({
selector: 'app-flyout-menu-item',
templateUrl: './flyout-menu-item.component.html',
styleUrls: ['./flyout-menu-item.component.scss']
})
export class FlyoutMenuItemComponent implements OnInit{
#ViewChild('menu') public menu;
#ViewChild('childMenu') public childMenu;
#ViewChild(MatMenuTrigger) public trigger: MatMenuTrigger;
#Input() featureInput: FeatureInput;
#Input() featureChoicesObject: FeatureChoice;
#Output() onOptionSelected: EventEmitter<FeatureInput> = new EventEmitter<FeatureInput>();
constructor(public solutionDataService: SolutionDataService) { }
ngOnInit() {
console.log(this.trigger);
}
ngOnChanges(simpleChanges: SimpleChanges) {
if (simpleChanges.menuOpen && simpleChanges.menuOpen.currentValue) {
setTimeout(() => {
// console.log(this.menu);
const itemsArray = this.childMenu.items.toArray();
for (let x = 0; x < itemsArray.length; x++) {
const menuItem = itemsArray[x];
if (this.featureInput.FeatureValue !== '' && menuItem._elementRef.nativeElement.value === this.featureInput.FeatureValue) {
menuItem._highlighted = true;
}
}
}, 1);
}
}
}
this.menuOpen = true;
Perhaps add menuOpen: boolean = false as an attribute at the top of your FlyoutComponent. I don't know where the value of menuOpen is saved.
the menuOpen property relates to the matMenuTrigger.
here's an example:
<button [ngClass]="{'active-icon': trigger.menuOpen}" type="button" mat-
icon-button #trigger="matMenuTrigger" [matMenuTriggerFor]="help">
<mat-icon></mat-icon>
</button>
<mat-menu #help="matMenu">
<div> textId </div>
</mat-menu>
We are using the ionic framework with iOS.
In the iOS emulator and in Safari browser, for one of our pages, clicking in a textarea shows the keyboard, and scrolls the textarea upwards so it is still viewable.
When the app is archived and processed through Apple iOS TestFlight, the behaviour is changed. Now, clicking in a textarea shows the keyboard, but the textarea no longer scrolls upwards so it is hidden.
Looks like something in the archival process is causing an issue.
Here's the code (there's another div above it). There's only the one textarea.
<div ng-if="!dy_compl">
<div class="wi-bottom item ">
<div ng-repeat="(key, dy) in element.dys">
<div id="wi-scroll-div" ng-if="key == dySel" style="height: {{scroller_height}}; overflow: scroll;">
<div>
<style>
p.wi-icon:before {
background: url("img/old_building.png") no-repeat !important;
}
</style>
<p class="wi-icon" ng-bind-html="dy.intro | to_trusted"></p>
</div>
<div ng-if="dy.ref">
<p class="wi-intro-my3" ng-bind-html="dy.ref.intro | to_trusted"></p>
<div ng-repeat="data in dy.ref.data track by $index">
<p class="wi-intro-my3-table" style="margin-left: 5%;" ng-bind-html="data | to_trusted"></p>
</div>
</div>
<label id="wi-input" class="item item-input item-stacked-label">
<span class="input-label" style="width:100%; max-width: 100%;">
<div class="wi-bottom-input-label" ng-bind-html="dy.notelabel | to_trusted"></div>
</span>
<textarea class="wi-bottom-input" ng-model="dy.note" type="text" placeholder="{{dy.note}}" ng-style="{'background-color': textAreaBackgroundColor}"></textarea>
</label>
<button class="wi-bottom-button button button-assertive col text-center" ng-click="dy.saved=true;saveNow()">
Save Notes
</button>
</br>
</div>
</div>
</div>
If you can't make it work with the plugin, check out this code I used on one project.. Looks a bit overhead, but it works:
Template:
<ion-content scroll-handle="user-profile-scroll">
<textarea maxlength="160" ng-model="currentUser.bio" ng-readonly="!editMode || focusAddInterestInput" placeholder="Write your bio..." class="user-bio">< </textarea>
</ion-content>
Controller:
$scope.windowHeight = window.innerHeight;
$scope.keyboardHeight = 0;
$scope.$on('$ionicView.loaded', function() {
var scrollView = {scrollTo: function() { console.log('Could not resolve scroll delegate handle'); }};
$timeout(function() {
var instances = $ionicScrollDelegate.$getByHandle('user-profile-scroll')._instances;
instances.length && (scrollView = instances[instances.length - 1]);
}).then(function() {
$scope.unbindShowKeyboardHandler = $scope.$on('KeyboardWillShowNotification', function(evt, info) {
$scope.keyboardHeight = info.keyboardHeight;
var input = angular.element(document.activeElement);
var body = angular.element(document.body);
var top = input.prop('offsetTop');
var temp = angular.element(input.prop('offsetParent'));
var tempY = 0;
while (temp && typeof(temp.prop('offsetTop')) !== 'undefined') {
tempY = temp.prop('offsetTop');
top += tempY;
temp = angular.element(temp.prop('offsetParent'));
}
top = top - (scrollView.getScrollPosition().top || 0);
var inputHeight = input.prop('offsetHeight');
var requiredSroll = $scope.windowHeight - $scope.keyboardHeight > top + inputHeight + 11 ? 0 : $scope.windowHeight - $scope.keyboardHeight - top - inputHeight - 12;
$timeout(function(){ scrollView.scrollTo(0, - requiredSroll || 0, true); });
});
$scope.unbindHideKeyboardHandler = $scope.$on('KeyboardWillHideNotification', function(evt, info) {
console.log(evt, info);
$scope.keyboardHeight = 0;
$timeout(function() { scrollView.scrollTo(0, 0, true); });
});
$scope.$on('$destroy', function() {
$scope.unbindShowKeyboardHandler();
$scope.unbindHideKeyboardHandler();
});
});
});
andm finally in app.js:
window.addEventListener('native.keyboardshow', keyboardShowHandler);
window.addEventListener('native.keyboardhide', keyboardHideHandler);
function keyboardShowHandler(info){
$rootScope.$broadcast('KeyboardWillShowNotification', info);
}
function keyboardHideHandler(info){
$rootScope.$broadcast('KeyboardWillHideNotification', info);
}
Turns out that we had one view that was manually disabling the keyboard scroll using:
cordova.plugins.Keyboard.disableScroll(true)
We weren't re-enabling this on a switch to another view.
The result is that in the emulator, the scroll disabling didn't traverse the scope to the new page, whereas after archival and TestFlight, it did.
Thanks for the other answers, comments.
How do I implement a button on-click event with loading different images?
I want to show different images in one div-container. If I click on the button "changeImage" it should change the picture.
How can I implement this in dart language?
Update: A new image should be loaded by each click. The images are in one file.
Do I call the img with a Button like this?
<input type="button" value="button" ng-click="img">
depending on the answer to my comment above there are different ways to do it.
(not tested)
import 'dart:async';
const NUM_IMAGES = 4;
main() {
int curImg = 1;
(querySelectorAll('img') as HtmlElement).forEach((img) {
img.onClick.listen((e) {
(querySelector('#img_${curImg % 4}') as HtmlElement).classes.add('hidden');
curImg++;
(querySelector('#img_${curImg % 4}') as HtmlElement).classes.remove('hidden');
});
}
.
<style>
img.hidden {
display: none;
}
</style>
<img id="img_1" src="a.gif" >
<img id="img_2" src="b.gif" class="hidden">
<img id="img_3" src="c.gif" class="hidden">
<img id="img_4" src="d.gif" class="hidden">
alternative solution:
List<String> uris = ['/img/img_a.gif', '/img/img_b.gif', '/img/img_c.gif', '/img/img_d.gif'];
main() {
int curImg = 0;
(imgElement = querySelector('img') as ImageElement).onClick.listen((img) {
curImg++;
imgElement.src = uris[curImg];
});
}
.
<img>