Start List at the bottom - javascript

I am using Ionic 2. I have a list of items:
this.firelist = this.dataService.findMessages(this.chatItem).map(items => {
this.updateReadMessages(items);
return items.reverse();
});
Displayed in a list:
<ion-content padding class="messages-page-content">
<ion-list class="message-list">
<ion-item class="message-item" *ngFor="let item of firelist | async">
....
This works, but as you can see, I have a reverse list. So the latest item is at the bottom. As a result, I would like to start the display at the bottom.
I have tried:
window.setTimeout(()=> {this.content.scrollToBottom();}, 2000);
This works, but there is a delay on the scroll, and visually the scroll doesn't look as good as if the list could just start at the bottom, and not have to scroll.
Is this possible?
Thanks

I doubt you will find a very elegant solution for this, but you can try the following:
Try using ionViewWillEnter:
ionViewWillEnter() {
this.content.scrollToBottom(0)
}
You could also try bind to the last item of your ngFor and then fire the scroll as the last item is rendered. Something similar to this:
<ion-item class="message-item" *ngFor="let item of firelist | async; let last = last">
{{ item }}
{{ last ? doScroll() : '' }}
</ion-item>
In your component:
export class somePage{
...
constructor(...) {
setTimeout(() => {
for (let i = 0; i < 100; i++) {
this.items[i] = i
}
}, 300)
}
doScroll(){
this.content.scrollToBottom(0)
}
}

Related

Free Drag with cdkDropList

I'm working on a project where I need to implement some sort of drop zone where you can drag element from a list, then drop them in a zone where they can be dragged freely. I also would like to use a cdkDropList for the zone, because it provides all the tools for connecting lists.
I used this example as a reference for my implementation, but I am not able to make it work right.
When I drop an item in the zone, it does not drop where the cursor was, it just goes to the top left of my zone, like it was in a list.
When I drag an item in the zone, it either drags correctly to where I want it to be dropped, gets dropped near the drop point, or just goes back to the top left.
Here is my cdkDrag element, it differs from the example linked above because I absolutely need it to be in it's own component (I would like to apply some logic to it in the future), but it is essentially the same concept (cdkDrag div in cdkDropList div). I managed to route all the needed events to the parent element (the zone) using Outputs.
<div class="element-box"
cdkDrag
cdkDragBoundary={{boundary_name}}
(cdkDragDropped)="dragDroppedEventToParent($event)"
(cdkDragStarted)="dragStartedEventToParent($event)"
(cdkDragMoved)="dragMovedEventToParent($event)">
{{object_name}}
<div *cdkDragPlaceholder class="field-placeholder"></div>
</div>
Here is the logic for the drag element:
export class ElementBoxComponent implements OnInit {
#Input () object_name: string;
#Input () boundary_name: string;
#Input () itemSelf: any;
#Output () dragMovedEvent = new EventEmitter<CdkDragMove>();
#Output () dragStartedEvent = new EventEmitter<CdkDragStart>();
#Output () dragDroppedEvent = new EventEmitter<any>();
constructor() {}
ngOnInit(): void {
}
dragMovedEventToParent(event: CdkDragMove) {
this.dragMovedEvent.emit(event);
}
dragStartedEventToParent(event: CdkDragStart){
this.dragStartedEvent.emit(event);
}
dragDroppedEventToParent(event: CdkDragEnd){
this.dragDroppedEvent.emit({event, "self": this.itemSelf});
}
}
Here is my drop zone element, where I render the drag elements (you can see that I routed the Outputs to methods in my logic for the zone):
<div class="drop-container"
#cdkBoard
cdkDropList
[id]="'cdkBoard'"
[cdkDropListData]="itemsInBoard"
[cdkDropListConnectedTo]="connectedTo"
cdkDropListSortingDisabled="true"
(cdkDropListDropped)="itemDropped($event)">
<app-element-box *ngFor="let item of itemsInBoard; let i=index"
object_name="{{item.name}}"
boundary_name=".drop-container"
itemSelf="{{item}}"
style="position:absolute; z-index:i"
[style.top]="item.top"
[style.left]="item.left"
(dragMovedEvent)="elementIsMoving($event)"
(dragStartedEvent)="startedDragging($event)"
(dragDroppedEvent)="stoppedDragging($event)"></app-element-box>
<svg-container class="bin-container" containerId="bin-image-container" *ngIf="_binVisible" height=40>
<svg-image class="bin-icon" [imageUrl]="BIN_ICON_URL" height=40 width=40></svg-image>
</svg-container>
</div>
And here are the relevant methods in the TS file for my drop zone:
itemDropped(event: CdkDragDrop<any[]>) {
if (event.previousContainer === event.container) {
moveItemInArray(this.itemsInBoard, event.previousIndex, event.currentIndex);
} else {
copyArrayItem(
event.previousContainer.data,
event.container.data,
event.previousIndex,
event.currentIndex
)
}
}
changePosition(event: CdkDragDrop<any>, field) {
const rectZone = this.dropZone.nativeElement.getBoundingClientRect();
const rectElement = event.item.element.nativeElement.getBoundingClientRect();
const top = rectElement.top + event.distance.y - rectZone.top;
const left = rectElement.left + event.distance.x - rectZone.left;
const out = (top < 0) || (left < 0) || (top > (rectZone.height - rectElement.height)) || (left > (rectZone.width - rectElement.width));
if (!out) {
event.item.element.nativeElement.style.top = top + 'px';
event.item.element.nativeElement.style.left = left + 'px';
} else {
this.itemsInBoard = this.itemsInBoard.filter((x) => x != event.item);
}
}
Again, the only differences are that my elements are encapsulated in their own components, and that the way I access the top and left style elements of the components are different (the code in the example did not work).
I know that the problem is the way I calculate the top and left variable, but I've been stuck on this for a week and cannot seem to find out what's wrong with it.
Here is a short demonstrative video if you want to better visualize what I am talking about.
Does anyone know what could be wrong with this ? I am open to any suggestions, thank you :)
It's difficut without a stackblitz know what is wrong
In this stackblitz I made two ckd-list
One cdkDropList (todoList) it's a typical "list", the other one is a dropZone (doneList). My elements are in the way
{label:string,x:number,y:number,'z-index':number}
The cdkDrag in the dropZone is in the way
<div cdkDrag class="item-box"
[style.top.px]="item.y"
[style.left.px]="item.x"
[style.z-index]="item['z-index']"
>
I choose that the todoList is connected to the dropZone, but the dropZone is not connected to anything. When I mover the elements of the dropZone if it's move away this one, simply add to the list
We need get the doneList as ElementRef
#ViewChild('doneList',{read:ElementRef,static:true}) dropZone:ElementRef;
And the neccesary functions
drop(event: CdkDragDrop<any[]>) {
if (event.previousContainer === event.container) {
moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
} else {
transferArrayItem(
event.previousContainer.data,
event.container.data,
event.previousIndex,
event.currentIndex,
);
event.item.data.y=(this._pointerPosition.y-this.dropZone.nativeElement.getBoundingClientRect().top)
event.item.data.x=(this._pointerPosition.x-this.dropZone.nativeElement.getBoundingClientRect().left)
this.changeZIndex(event.item.data)
}
this.posInside={source:null,x:0,y:0}
}
moved(event: CdkDragMove) {
this._pointerPosition=event.pointerPosition;
}
changeZIndex(item:any)
{
this.done.forEach(x=>x['z-index']=(x==item?1:0))
}
changePosition(event:CdkDragDrop<any>,field)
{
const rectZone=this.dropZone.nativeElement.getBoundingClientRect()
const rectElement=event.item.element.nativeElement.getBoundingClientRect()
let y=+field.y+event.distance.y
let x=+field.x+event.distance.x
const out=y<0 || x<0 || (y>(rectZone.height-rectElement.height)) || (x>(rectZone.width-rectElement.width))
if (!out)
{
field.y=y
field.x=x
}
else{
this.todo.push(field)
this.done=this.done.filter(x=>x!=field)
}
}
The .html like
<div class="wrapper">
<div
cdkDropList
#todoList="cdkDropList"
[cdkDropListData]="todo"
[cdkDropListConnectedTo]="[doneList]"
class="example-list"
(cdkDropListDropped)="drop($event)"
>
<div
class="example-box"
*ngFor="let item of todo"
cdkDrag
[cdkDragData]="item"
(cdkDragMoved)="moved($event)"
>
{{ item.label }}
<div *cdkDragPlaceholder class="field-placeholder"></div>
</div>
</div>
<div
cdkDropList
#doneList="cdkDropList"
[cdkDropListData]="done"
class="drag-zone"
cdkDropListSortingDisabled="true"
(cdkDropListDropped)="drop($event)"
>
<ng-container *ngFor="let item of done">
<div
cdkDrag
class="item-box"
[style.top.px]="item.y"
[style.left.px]="item.x"
[style.z-index]="item['z-index']"
(cdkDragStarted)="changeZIndex(item)"
(cdkDragDropped)="changePosition($event, item)"
>
{{ item.label }}
<div *cdkDragPlaceholder class="field-placeholder"></div>
</div>
</ng-container>
</div>
</div>

Use Vuetify v-btn with loader inside a v-for loop

I am trying to figure out how to have my dynamically generated buttons inside a for loop each have a separate loader. Vuetify has buttons with Loaders.
The problem I am having is, When these buttons are in a for loop and one is clicked they all show the loading indicator. I would like only the one that is clicked to show the loading indicator.
HTML:
<div v-for="(item, i) in items" :key='i'>
<v-btn
dark
color="pink"
:loading="loading"
#click="loader = 'loading'"
>
<v-icon>location_on</v-icon> Lookup
<span slot="loader">locating...</span>
<span slot="loader" class="custom-loader">
<v-icon dark>cached</v-icon>
</span>
</v-btn>
</div>
SCRIPT
data () {
return {
loader: null,
loading: false
}
},
Let's say I have 3 items. The code above will generate three buttons but they all will share the same loading param. How can I have each button use its only loading param? As always any and all help is much appreciated.
You're using the same data property for all buttons, so these buttons share the same loading state which affects the at one time, to make difference try to add a data property called index which represents the current clicked button index :
data () {
return {
index:-1,
loader: null,
loading: false
}
},
and bind loading prop to the condition loading && i==index and update the current index on click event #click="loader = 'loading';index=i" :
<div v-for="(item, i) in items" :key='i'>
<v-btn
dark
color="pink"
:loading="loading && i==index"
#click="loader = 'loading';index=i"
>
<v-icon>location_on</v-icon> Lookup
<span slot="loader">locating...</span>
<span slot="loader" class="custom-loader">
<v-icon dark>cached</v-icon>
</span>
</v-btn>
</div>
Its actually a lot easier than you think:
<div v-for="(item, i) in items" :key='i'>
<v-btn
dark
color="pink"
:loading="loading[index]"
#click="loading[index] = true"
>
<v-icon>location_on</v-icon> Lookup
<span slot="loader">locating...</span>
<span slot="loader" class="custom-loader">
<v-icon dark>cached</v-icon>
</span>
</v-btn>
</div>
data () {
return {
loading: {},
}
},
In the beginning loading[index] will be undefined, so it will be evaluated as false, once you establish its value in the click event it will be evaluated as true, it worked for me, hope it helps.
you can use reactive array to prevent changing the index like this.
<div v-for="(item, i) in items" :key='i'>
<v-btn #click.prevent="testload(i)" :loading="loading[i]"></v-btn>
</div>
data () {
return {
loading: [],
}
},
methods: {
testload: function (index) {
// reactive array
this.$set(this.loading, index, true);
console.log(index)
console.log(this.loading[index])
// stop loading after x miliseconds
setTimeout(() => (this.$set(this.loading, index, false)), 3000)
},
dont forget to input false according the length of items, this is the example:
getDataAll: function () {
var i = 0
for (i = 0; i < items.length; i++) {
this.loading.push(false);
}
}

Angular: unable to scroll down to bottom in element

I've been postponing fixing this error that I have been having for a while now. I have the below chatwindow:
The window where I display the messages is a separate component (chat-window.component.ts). I want to scroll to the bottom with ngOnChanges.
When we receive the conversation with the messages from the parent component, where it is received from the server via an asynchronous request, we want to scroll to the bottom of the window element. We do this by calling the this.scrollToBottom() method of the class in the ngOnChanges lifecycle hook.
This.scrollToBottom does get called, but it doesn't scroll to the bottom of the element. Can someone see why?
chat-window.component.ts: in ngOnchanges we do some synchronous stuff before we call this.scrollToBottom()
export class ChatboxWindowComponent implements OnChanges, OnInit, AfterViewChecked {
#Input('conversation') conversation;
#ViewChild('window') window;
constructor() { }
ngOnChanges() {
// If the date separators have already been added once, we avoid doing it a second time
const existingDateObj = this.conversation.messages.findIndex((item, i) => item.dateObj);
if (existingDateObj === -1) {
this.conversation.messages.forEach( (item, index, array) => {
if (index !== 0) {
const date1 = new Date(array[index - 1].date);
const date2 = new Date(item.date);
if (date2.getDate() !== date1.getDate() || date2.getMonth() !== date1.getMonth()) {
this.conversation.messages.splice(index, 0, {date: date2, dateObj: true});
console.log(this.conversation.messages.length);
}
}
});
}
this.scrollToBottom();
}
ngOnInit() {
}
ngAfterViewChecked() {
}
isItMyMsg(msg) {
return msg.from._id === this.conversation.otherUser.userId;
}
scrollToBottom() {
try {
console.log('scrollToBottom called');
this.window.nativeElement.top = this.window.nativeElement.scrollHeight;
} catch (err) {}
}
}
chat-window.component.html
<div #window class="window">
<ng-container *ngFor="let message of conversation.messages">
<div class="date-container" *ngIf="!message.msg; else windowMsg">
<p class="date">{{message.date | amDateFormat:'LL'}}</p>
</div>
<ng-template #windowMsg>
<p
class="window__message"
[ngClass]="{
'window__message--left': isItMyMsg(message),
'window__message--right': !isItMyMsg(message)
}"
>
{{message.msg}}
</p>
</ng-template>
</ng-container>
</div>
The scroll doesn't work because the list of messages is not rendered yet when you call scrollToBottom. In order to scroll once the messages have been displayed, set a template reference variable (e.g. #messageContainer) on the message containers:
<ng-container #messageContainer *ngFor="let message of conversation.messages">
...
</ng-container>
In the code, you can then access these elements with ViewChildren and scroll the window when the QueryList.changes event is triggered:
#ViewChildren("messageContainer") messageContainers: QueryList<ElementRef>;
ngAfterViewInit() {
this.scrollToBottom(); // For messsages already present
this.messageContainers.changes.subscribe((list: QueryList<ElementRef>) => {
this.scrollToBottom(); // For messages added later
});
}
You can add the following code into your HTML element.
#window [scrollTop]="window.scrollHeight" *ngIf="messages.length > 0"
Full code according to your code sample as follows,
<div #window [scrollTop]="window.scrollHeight" *ngIf="messages.length > 0" class="window">
<ng-container *ngFor="let message of conversation.messages">
<div class="date-container" *ngIf="!message.msg; else windowMsg">
<p class="date">{{message.date | amDateFormat:'LL'}}</p>
</div>
<ng-template #windowMsg>
<p
class="window__message"
[ngClass]="{
'window__message--left': isItMyMsg(message),
'window__message--right': !isItMyMsg(message)
}"
>
{{message.msg}}
</p>
</ng-template>
</ng-container>
</div>
This is work for me. (Currently, I'm using Angular 11) 😊👍

show the index number of slides Ionic

I'm working on an app, I've created some slides but I need to show the index number of slides instead of pages.
I want to do something like this:
There's a way of doing this, but you'll need to save your slides in an array in order for this to work. You'll also need a property in your class to show what's the current page.
So in your page .ts file:
export class YourPage {
// the current page number
public page: number = 0;
// your slides
public mySlides = [...your slides...];
// when the slides changes this method is triggered, needs to be +1 since the index starts at 0
slideChanged = ev => this.page = ev.realIndex + 1;
}
And then your page html file
<ion-slides pager="false" (ionSlideDidChange)="slideChanged($event)">
<ion-slide *ngFor="let slide of mySlides ">
<!-- your slides code -->
</ion-slide>
</ion-slides>
<p text-center>{{page}} of {{mySlides.length}}</p>
Hope this helps.
You can write something like:
HTML:
<ion-slides pager="true" paginationType="fraction">
Try this
ionViewWillEnter(){
this.ionSlides.length().then(number => {
console.log(number);
this.allNumber = number;
});
}
sliderDoChange() {
this.ionSlides.getActiveIndex().then(index => {
this.currentNumber = index;
});
}

Angular 2 change ngFor items class knowing only it's index

I have a lot of buttons in my *ngFor, and I want that when someone click's on a button - it becomes active(it gets active class).
What I've done :
HTML :
<button
[ngClass]="{'activeBtn': buttActive }"
(click)="addDistrict(item);changeActive(i)"
*ngFor="let item of items; let i = index"
ion-button
#disable>
{{item.name}}
</button>
TS : (changing all buttons class to active (i want to change only that one i clicked)
buttActive = false;
changeActive(i) {
console.log(i);
this.buttActive = !this.buttActive;
}
have a buttActive property in the object and change it
button [ngClass]="{'activeBtn': item.buttActive }" (click)="addDistrict(item);changeActive(item,i)"
*ngFor="let item of items; let i = index" ion-button #disable>{{item.name}}</button>
changeActive(item, i){
console.log(i);
item.buttActive = !item.buttActive;
}
If you don't want to create a property on each item, then create a lastClickedIndex property in your Component class and set it with the index of the button that was clicked:
lastClickedIndex;
changeActive(i) {
this.lastClickedIndex = i;
}
And in your template, check for the lastClickedIndex button based on index to apply the activeBtn class.
<button
*ngFor="let item of items; let i = index"
[ngClass]="(lastClickedIndex == i) ? 'activeBtn': ''"
(click)="addDistrict(item);changeActive(i)"
ion-button
#disable>
{{item.name}}
</button>
That way you won't have to create a property on each item object. This will also take care of removing the class from the previously selected button when some other button is clicked.
Here's a StackBlitz for your ref.

Categories

Resources