Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue when setting option values via function #52

Closed
MasterPuffin opened this issue Jan 6, 2025 · 11 comments
Closed

Issue when setting option values via function #52

MasterPuffin opened this issue Jan 6, 2025 · 11 comments

Comments

@MasterPuffin
Copy link

Hello,
I found another issue:
I have an object where I use one value for a select field. When the value is set to null and initializing sprae, the dropdown is empty. When I now set the available values to an empty array and then push entries to the available values via a function, now the dropdown has selected a value. However if I now read from the original object the value set by the dropdown is still null.

Please see the following example code to reproduce this issue

<div id="container">
  <select
          :value="object.defaultmode">
    <option :each="option in options"
            :value="option.uid"
            :text="option.name"></option>
  </select>
    <button :onclick="()=>{toggle()}">Toggle</button>
    <button :onclick="()=>{dump()}">Dump</button>
</div>
const defaultOptions = [
    {uid: '1', name: 'Option 1'},
    {uid: '2', name: 'Option 2'}

]

const container = document.querySelector('#container')

const state = sprae(container, {
    options: defaultOptions,
    object: {defaultmode: null},
    toggle() {
        if (state.options.length > 0) {
            state.options = []
        } else {
            state.options = defaultOptions
        }
    },
    dump() {
        console.log(JSON.parse(JSON.stringify(state.object)))
    }
})
@dy
Copy link
Owner

dy commented Jan 6, 2025

Thanks for the catch @MasterPuffin. Please try v11.0.3 - it must be fixed there.

Unfortunately I cannot see more reliable workaround than mutation observer. It's specific case of selects - they automatically change own value if children added/changed/removed, and no input/change event is produced, so that's something outside of sprae scope.

Watch out: state gets updated in a tick.

@MasterPuffin
Copy link
Author

The issue is fixed wirh 11.0.3.
However I want to have the select blank after the change. I wrote the following function to do this

(async () => {
    new Promise(resolve => setTimeout(resolve, 0));
    state.object.defaultmode = null
 })();

It has to be this complex, because, as you mentioned, the update happens after one tick.

@dy
Copy link
Owner

dy commented Jan 7, 2025

Have you tried without delay @MasterPuffin ? It should not force you to do that, it should just work.
The only limitation this imposes is that state.object.defaultmode is not immediately reflected when you change options like state.options = [] or state.options = defaultOptions

@MasterPuffin
Copy link
Author

I just tested it again to make sure, but without the delay it doesn't work for me.

@dy
Copy link
Owner

dy commented Jan 7, 2025

Hmm, that's not a way to go. Is that the test-case above? Would you be able to write basic example when it fails? I will try to make it sync if that's indeed the cause.

@dy dy reopened this Jan 7, 2025
@MasterPuffin
Copy link
Author

I managed to reproduce the issue in a test case

<div id="container">
    <select class="form-control"
            :value="object.defaultmode">
        <option :each="option in getAvailableOperationmodes(object.operationmodes)"
                :value="option.uid"
                :text="option.name"></option>
    </select>
    <div :each="option in object.operationmodes">
        <button :onclick="()=>{toggle(option)}">Toggle <span :text="option.name"></span></button>
    </div>
    <button :onclick="()=>{dump()}">Dump</button>
</div>
const defaultOptions = [
    {
        "uid": "1",
        "name": "Test",
        "enabled": true,
    },
    {
        "uid": "2",
        "name": "Test 2",
        "enabled": true,
    }
]

const object = {
    "defaultmode": null,
    "operationmodes": defaultOptions
}

const container = document.querySelector('#container')

const state = sprae(container, {
    object: object,
    toggle(option) {
        option.enabled = !option.enabled;
        if (!object.defaultmode) {
            (async () => {
                await new Promise(resolve => setTimeout(resolve, 0));
                state.object.defaultmode = null
            })();
        }
    },
    dump() {
        console.log(JSON.parse(JSON.stringify(state.object)))
    },
    getAvailableOperationmodes(objects) {
        return objects.filter(o => {
            return o.enabled
        })
    }
})

I use the latest sprae.umd.min.js without any other code, if that matters.

@dy
Copy link
Owner

dy commented Jan 8, 2025

Can you elaborate this part:

if (!object.defaultmode) {
            (async () => {
                await new Promise(resolve => setTimeout(resolve, 0));
                state.object.defaultmode = null
            })();
        }

What are you trying to achieve/test here? Normally it's supposed object to be only passed to sprae once and forgotten, instead work with state.object.
I played around with your example - and besides this part it works as expected. I want to understand where the struggle is.

Also - I've updated to v11.0.4 with better mutation observer init order, IDK if it will make difference but you can test.

@MasterPuffin
Copy link
Author

Sorry for the late reply.
My goal is, that if there is no entry selected in the dropdown, when toggling an option, there should be still no entry selected in the dropdown.
This example works as intended, however it is a workaround. When there is no entry selected and an option is toggled, the selected option is set to null again.

toggle(option) {
        option.enabled = !option.enabled;
        if (!state.object.defaultmode) {
            (async () => {
                await new Promise(resolve => setTimeout(resolve, 0));
                state.object.defaultmode = null
            })();
        }
    },

However the following code should also work to reset the selected option back to null. This is not the case.

toggle(option) {
        option.enabled = !option.enabled;
        if (!state.object.defaultmode) {
             state.object.defaultmode = null
        }
    },

@dy
Copy link
Owner

dy commented Feb 19, 2025

However the following code should also work

What I can see in reproduction, both your snippets work identical on my side, there's no need for async wrapper.

Please tell me if I understand your issue right.

If we don't have this guard condition:

if (!state.object.defaultmode) state.object.defaultmode = null

then when you toggle/remove an item from the options list, it automatically selects the first available option, even if that was selection was null (unselected). So you would prefer to keep selected item empty when we modify options, right?

Image

In other words, you don't want to have this line if (!state.object.defaultmode) state.object.defaultmode = null at all, right?

@MasterPuffin
Copy link
Author

then when you toggle/remove an item from the options list, it automatically selects the first available option, even if that was selection was null (unselected). So you would prefer to keep selected item empty when we modify options, right?

Yes, that's exactly what I'm trying to achieve, when no option is selected, after adding or removing items, there should be still no option selected.

@dy
Copy link
Owner

dy commented Feb 27, 2025

So it means preserving selected option if list of options changes, even if new list doesn't include that.

By default HTMLSelect selects first available option whenever it's available. Right now we follow that behaviour: when you add an option, select goes from null to first option. When you delete an option, select goes to next available option. When you delete all options it resets selection to null.

It seems risky to attempt to rewrite default HTML behaviour.

I think what you're doing with if (!state.object.defaultmode) state.object.defaultmode = null is not a workaround but actual piece of logic that you need in your app, since it redefines select behavior.

Please reopen if you find it an issue.

@dy dy closed this as completed Feb 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants