Ionic version:
[x] 4.x
Current behavior:
The second time the PickerController shows the options are not showing correctly.
Expected behavior:
It should show always the right options
Steps to reproduce:
Related code:
Other information:
Ionic info:
Ionic:
ionic (Ionic CLI) : 4.10.3 (/usr/local/lib/node_modules/ionic)
Ionic Framework : @ionic/angular 4.0.2
@angular-devkit/build-angular : 0.13.4
@angular-devkit/schematics : 7.3.4
@angular/cli : 7.3.4
@ionic/angular-toolkit : 1.4.0
System:
NodeJS : v10.15.1 (/usr/local/bin/node)
npm : 6.8.0
OS : macOS Mojave
Hi there,
Thanks for the issue. Can you share a repository with the code required to reproduce this issue? Thanks!
Here's a simple example (home.page.ts) from a new blank project. Hope this helps...
import { Component } from '@angular/core';
import { PickerController } from '@ionic/angular';
@Component({
selector: 'app-home',
templateUrl: 'home.page.html',
styleUrls: ['home.page.scss'],
})
export class HomePage {
values = [
{ value: 1, text: 'value 1' },
{ value: 2, text: 'value 2' },
{ value: 3, text: 'value 3' },
{ value: 4, text: 'value 4' },
{ value: 5, text: 'value 5' }
]
_selectedIndex: number = 0;
constructor(private pickerCtrl: PickerController) { }
async showPicker() {
const picker = await this.pickerCtrl.create({
buttons: [
{ text: 'Cancel' },
{
text: 'Done',
handler: (value) => {
const v = value.pick.value;
this._selectedIndex = this.values.findIndex(i => i.value == v);
console.log(this._selectedIndex);
}
}
],
columns: [
{
name: 'pick',
// if selectedIndex is set through a variable the view is messed up
// selectedIndex: 2, // this works
selectedIndex: this._selectedIndex, // this messes up the view
options: this.values
}
]
});
picker.present();
}
}
Thanks for the code!
Which browser/version are you testing this on? This is what I see when I try it out in a blank Ionic starter app (ionic start myApp blank
):
@liamdebeasi hi. i am using Chrome 72.
Here are the step that I got the issue:
The first time you select the controller it shows correctly.
Then pick an option say "value 3" and click Ok.
Then the second time you show the picker the options don't show correctly if the selectedIndex is set.
I hope this helps
Thanks for the follow up! I can reproduce this issue. We will look into it!
I have the same issue.
You can test it using the following methods:
Modify the picker-column.tsx file:
1.modify this function:
` private update(y: number, duration: number, saveY: boolean) {
if (!this.optsEl) {
return;
}
let translateY = 0;
let translateZ = 0;
const { col, rotateFactor } = this;
const selectedIndex = col.selectedIndex = this.indexForY(-y);
const durationStr = (duration === 0) ? '' : duration + 'ms';
const scaleStr = 'scale(${this.scaleFactor})';
const children = this.optsEl.children;
const children_length = this.optsEl.children.length;
const options_length = col.options.length;
const length = children_length < options_length ? options_length : children_length;
for (let i = 0; i < length; i++) {
const button = children[i] as HTMLElement;
const opt = col.options[i];
const optOffset = (i * this.optHeight) + y;
let transform = '';
if (rotateFactor !== 0) {
const rotateX = optOffset * rotateFactor;
if (Math.abs(rotateX) <= 90) {
translateY = 0;
translateZ = 90;
transform = ‘rotateX(${rotateX}deg) ‘;
} else {
translateY = -9999;
}
} else {
translateZ = 0;
translateY = optOffset;
}
const selected = selectedIndex === i;
transform += ‘translate3d(0px,${translateY}px,${translateZ}px) ‘;
if (this.scaleFactor !== 1 && !selected) {
transform += scaleStr;
}
// Update transition duration
if (this.noAnimate) {
if(opt){
opt.duration = 0;
}
if(button){
button.style.transitionDuration = '';
}
} else if (opt){
if (duration !== opt.duration) {
opt.duration = duration;
if(button){
button.style.transitionDuration = durationStr;
}
}
}
// Update transform
if(opt){
if (transform !== opt.transform) {
opt.transform = transform;
if(button){
button.style.transform = transform;
}
}
}
// Update selected item
if(opt){
if (selected !== opt.selected) {
opt.selected = selected;
if (selected && button) {
button.classList.add(PICKER_OPT_SELECTED);
} else if (button){
button.classList.remove(PICKER_OPT_SELECTED);
}
}
}
}
this.col.prevSelected = selectedIndex;
if (saveY) {
this.y = y;
}
if (this.lastIndex !== selectedIndex) {
// have not set a last index yet
hapticSelectionChanged();
this.lastIndex = selectedIndex;
}
}
2.modify this function:
render() {
const col = this.col;
const Button = 'button' as any;
return [
col.prefix && (
@liamdebeasi: I think @zwlccc is on the right track, here.
Other observations: When trying to recreate the issue using the base ionic code base, I could not get it to occur. Perhaps it's something that is only present in the Angular bindings?
I have same issue too,Expect good news
@nvahalik No, this is a problem with rendering styles. Dynamically set options. A rendering style error occurs,When the number of new options is greater than the old number of options.
it is the problem of changed columns.options with dynamically set options, when click the picker without changing the selectedIndex twice, the columns.options will add three attribute of selected/duration/transform, just delete it.
columns.options.forEach(element => {
delete element.selected;
delete element.duration;
delete element.transform;
});
Indeed! That does indeed fix it for me.
@MengLu2016 This doesn't work for my components.ionic4-city-picker
I was able to reproduce it, but was able to workaround it. The issue seems to be the way the values are set for the PickerColumnOptions. I did not have any issue with SelectedIndex being set dynamically. Note that the Ionic DatePicker component (see src when you have a chance) uses PickerController .
In my case I ended up with below.......................
You could do either
OR
.......
createPickerColumnOptions() : PickerColumnOption[] {
let hoursData:PickerColumnOption[] = [
{ text: 'Every 1 hour', value: '1' },
{ text: 'Every 2 hours', value: '2' },
{ text: 'Every 3 hours', value: '3' },
{ text: 'Every 4 hours', value: '4' }
];
console.log('Data is ' + JSON.stringify(hoursData));
return hoursData;
}
..........
..........
columns: [
{
name: 'hours',
selectedIndex: this.hrlyReminder.repeat-1, // this is where we set the default selection
options: this.createPickerColumnOptions()
}
]
I have the same issue using chrome browser. The solution by @MengLu2016 is working perfectly.
Ionic:
Ionic CLI : 5.2.3 (AppData\Roaming\npm\node_modules\ionic)
Ionic Framework : @ionic/angular 4.6.2
@angular-devkit/build-angular : 0.13.9
@angular-devkit/schematics : 7.3.9
@angular/cli : 7.3.9
@ionic/angular-toolkit : 1.5.1
System:
Android SDK Tools : 26.1.1 (Android\Sdk)
NodeJS : v10.0.0 (nodejs\node.exe)
npm : 6.9.0
OS : Windows 10
@MengLu2016 Could you show a full demo of your workaround? I just tried and it doesn't work for me. Thanks.
@michaelins
async numberPicker() {
const picker = await this.pickerCtrl.create({
buttons: [
{
text: 'Cancel',
role: 'cancel'
},
{
text: 'Confirm',
handler: (data) => {
console.log(data.number);
}
}
],
columns: [{
name: 'number',
options: [
{ text: 'One', value: 1 },
{ text: 'Two', value: 2 },
]
}]
});
picker.columns[0].options.forEach(element => {
delete element.selected;
delete element.duration;
delete element.transform;
});
picker.present();
}
@liamdebeasi is there a fix coming soon? I really need this...
@a7md0 thank you but didn't work for me. When I update the columns i.e. replace column options with new ones, all new options are stacked again.
I had the exact same problem and was able to fix it using the techniques from this thread. THANK YOU everyone! I'm doing this on picker dismissal. Changed it to work for multi-column pickers as well:
async showPicker() {
let opts: PickerOptions = {
buttons: [{ text: 'Cancel', role: 'cancel' }, { text: 'Done' }],
columns: [{ name: 'Name', options: this.options, selectedIndex: 0 }],
};
let picker = await this.pickerCtrl.create(opts); picker.present();
picker.onDidDismiss().then(async data => {
// ... program logic
picker.columns.forEach(col => { col.options.forEach(el => { delete el.selected; delete el.duration; delete el.transform; }) })
});
}
@jwhiteaei Thank you for your help. I will try to see if this method works in my component.
It looks like the same PickerOptions
object must not be reused for several pickers. I. e. if you store options in component instance, do a copy before passing to pickerController.create()
:
const opts = JSON.parse(JSON.stringify(this.opts));
const picker = await this.pickerController.create(opts);
Hi there,
I think we are dealing with two separate problems here:
1- If you get all your options stacked once you reopen the picker, then the solution suggested by @MengLu2016 should solve your problem
2- If you are working with dynamic options, in my case I have 2 columns where the options in column B depends on the value selected by column A. You need to make sure that options in column B always have the same length. Otherwise, you might see stacked options if the number of the new options is greater than the number of the old options as @zwlccc said. So what I did, is to make sure that all possible options have the same length by adding disabled options by the end of the smaller one.
For example:
PossibleOptionsForColumnB = [
[
{ value: 1, text: 'value 1' },
{ value: 2, text: 'value 2' },
{ value: 3, text: 'value 3' },
{ value: 4, text: 'value 4' },
{ value: -1, text: '', disabled: true }
],
[
{ value: 1, text: 'value 1' },
{ value: 2, text: 'value 2' },
{ value: 3, text: 'value 3' },
{ value: 4, text: 'value 4' },
{ value: 5, text: 'value 5' }
]
,
[
{ value: 1, text: 'value 1' },
{ value: 2, text: 'value 2' },
{ value: 3, text: 'value 3' },
{ value: -1, text: '', disabled: true },
{ value: -1, text: '', disabled: true }
]
];
@halkhateeb your solution works like a charm! Thank you for saving my day.
It partially works for me, once I open the PickerController for the second time, I can see it all stacked up and then animating to their right positions.
What I need is for it to already be organized once I open a second time.
EDIT: I was actually able to make it work by using the solution by @MengLu2016, but instead of deleting everything after Dismiss, I deleted everything before rendering the picker.
@Galiza i've got the same problem as you but your solution doesn't work for me. Any ideas why?
@nurizzatiabdharis Very strange, here's how I solved it:
async openPicker() {
this.removeAllAddedAttributesToArray();
const pickerElement = await this.pickerCtrl.create({
buttons: [
{
text: CANCEL,
cssClass: 'cancel-btn',
role: 'cancel'
},
{
text: DONE,
cssClass: 'done-btn',
role: 'done',
handler: (data) => {
this.changeValue(data.value);
}
}
],
columns: [{
name: 'col',
options: this.simpleColumns,
selectedIndex: this.selectedIndex
}],
cssClass: 'ion-picker',
});
await pickerElement.present();
}
private removeAllAddedAttributesToArray() {
this.simpleColumns.forEach((data) => {
if (data.duration) {
delete data.transform;
delete data.duration;
delete data.selected;
}
});
}
This work for me @nurizzatiabdharis thanks.
I got the same issue when use dynamic column options. Just as @zwlccc @halkhateeb, if the new options has short length than current one, overlay will happen.
Can be produced in both ionic 4.x and 5.x.
@halkhateeb gives a nice workaround to add some disabled options, till now it seems as the only solution.
Experiencing the same issue.. Running ionic latest(5.x.)
what solved the issue was declaring
function removeAllAddedAttributesToArray() {
data.forEach(data => {
delete data.transform;
delete data.duration;
delete data.selected;
});
}
and calling it above the const picker = await pickerController.create({
Any solution for dynamic column options?
I tried disabling the options, but then I had a strange behavior. The options are no longer displayed, but still selectable.
And besides, it does not look good if there are such huge gaps.
Having the same issues on Ionic 5 React. First time opening the picker, the options are showing fine. Close it and open again the options will overlap.
Same problem on Ionic 5 Angular.
Same issue on Ionic 5 Angular
For those having issue with using IonPicker component in React. Here's my solution:
Place sortOptions inside the render() or if using functional component then place it inside the function declaration like below:
const sortOptions: PickerColumn[] = [
{
name: "sortOption",
options: [
{ text: "Apple", value: "red" },
{ text: "Banana", value: "yellow" },
{ text: "Orange", value: "orange" },
],
},
];
And in your IonPicker it stays the same
<IonPicker
//...
columns={sortOptions}
//...
/>
It re-creates sortOptions on every render (not optimal but it is what it is until the "real" fix)
@gusterwoei FYI
I had the same problem. Turned around the picker has issues if you also reuse column options, in my case I used a array of 60 values (for minutes and seconds) two times and the picker didn't display the seconds correctly on first time opening, while also showing the stacked items on all secondary opens.
So I
Thank you all!
Hi there,
I think we are dealing with two separate problems here:1- If you get all your options stacked once you reopen the picker, then the solution suggested by @MengLu2016 should solve your problem
2- If you are working with dynamic options, in my case I have 2 columns where the options in column B depends on the value selected by column A. You need to make sure that options in column B always have the same length. Otherwise, you might see stacked options if the number of the new options is greater than the number of the old options as @zwlccc said. So what I did, is to make sure that all possible options have the same length by adding disabled options by the end of the smaller one.
For example:
PossibleOptionsForColumnB = [ [ { value: 1, text: 'value 1' }, { value: 2, text: 'value 2' }, { value: 3, text: 'value 3' }, { value: 4, text: 'value 4' }, { value: -1, text: '', disabled: true } ], [ { value: 1, text: 'value 1' }, { value: 2, text: 'value 2' }, { value: 3, text: 'value 3' }, { value: 4, text: 'value 4' }, { value: 5, text: 'value 5' } ] , [ { value: 1, text: 'value 1' }, { value: 2, text: 'value 2' }, { value: 3, text: 'value 3' }, { value: -1, text: '', disabled: true }, { value: -1, text: '', disabled: true } ] ];
@halkhateeb How to avoid column dependancy?
I'm using picker.addEventListener('ionPickerColChange', async (event: any) => {} to refresh column but not works!
Upgrading some packages it's was solved.
"@ionic/react": "^5.3.4",
"@ionic/react-router": "^5.3.4",
To upgrade just run:
npm update
Most helpful comment
@michaelins