2

I have two input fields, the idea is that both can change the value of the other one.

if a value of 1 - 200 is entered into the level field, Vue looks up the level and returns the amount of points for that level and enters them into the points field.

But I also want the inverse to happen, so if a user enters an amount of points they have in the points field Vue looks up the points and returns the level associated with that amount of points.

This works except Vue does a double calculation as it is watching both fields so if I enter 300 in the points field it correctly returns level 4 but then Vue detects the change in the level field and then changes the points to 250.

I only want Vue to detect when the user changes the field instead of re-calculating after it changes the field.

I hope this makes sense, it's a hard one to explain.

A sample of the data returned from the axios request:-

[
    {"level":1,"points":0},
    {"level":2,"points":72},
    {"level":3,"points":175},
    {"level":4,"points":250},
    {"level":5,"points":401,}
]

a simplified vue component to demonstrate my working:-

<template>
    <div>
        <div class="card-body">
            <form>
                <div class="row">
                    <div class="col">
                        <div class="form-group">
                            <input type="text" v-model="currentLevel" class="form-control">
                        </div>
                        <div class="form-group">
                            <input type="text" v-model="currentPoints" class="form-control">
                        </div>
                    </div>
                </div>
            </form>
        </div>
    </div>   
</template>

<script>
    export default { 
        data() {
            return {
                currentLevel: 1,
                currentPoints: 0,
                pointsTable: []
            }
        },
        mounted() {
            axios.get('/api/points').then(this.fetchPointsTable);
        },
        methods: {
            fetchPointsTable({data}) {
                this.pointsTable = data;
            }
        },
        watch: {
            currentLevel: function() { 
                const result = this.pointsTable.find( ({ level }) => level === parseInt(this.currentLevel) );

                this.currentPoints = result.experience;
            },
            currentPoints: function() {
                var levels = [];
                var goal = this.currentXp;

                var closest = this.xpTable.reduce(function(prev, curr) {
                    if (curr.points <= goal) {
                        levels = curr.level;
                    }
                    return Math.max(levels);
                });

                this.currentLevel = closest;
            }
        }
    }
</script>

1 Answer 1

1

You're looking for the change event in this case, which will activate specifically when a user alters the value of an input.

From MDN (emphasis mine):

The change event is fired for <input>, <select>, and <textarea> elements when an alteration to the element's value is committed by the user. Unlike the input event, the change event is not necessarily fired for each alteration to an element's value.

This means that whenever a user changes the value in either input, the change event will fire, triggering the associated method and updating the other box. This happens without any interference since the other input won't fire a change event when its value is updated programmatically (unlike a watcher, which tracks all value changes), and therefore won't trigger its associated method.

Tweaked version of your example:

<template>
    <div>
        <div class="card-body">
            <form>
                <div class="row">
                    <div class="col">
                        <div class="form-group">
                            <input
                                type="text"
                                v-model="currentLevel" 
                                class="form-control" 
                                @change="updateCurrentPoints"
                            >
                        </div>
                        <div class="form-group">
                            <input
                                type="text"
                                v-model="currentPoints"
                                class="form-control"
                                @change="updateCurrentLevel"
                            >
                        </div>
                    </div>
                </div>
            </form>
        </div>
    </div>   
</template>

<script>
    export default { 
        data() {
            return {
                currentLevel: 1,
                currentPoints: 0,
                pointsTable: []
            }
        },
        mounted() {
            axios.get('/api/points').then(this.fetchPointsTable);
        },
        methods: {
            fetchPointsTable({data}) {
                this.pointsTable = data;
            },
            updateCurrentPoints() { // contents of currentLevel watcher
                const result = this.pointsTable.find( ({ level }) => level === parseInt(this.currentLevel) );

                this.currentPoints = result.experience;
            },
            updateCurrentLevel() { // contents of currentPoints watcher
                var levels = [];
                var goal = this.currentXp;

                var closest = this.xpTable.reduce(function(prev, curr) {
                    if (curr.points <= goal) {
                        levels = curr.level;
                    }
                    return Math.max(levels);
                });

                this.currentLevel = closest;
            },
        }
    }
</script>
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for the answer, this works, however it does not fire the event until after the change has been commited, i.e. the user has to change focus from that input field. I was hoping to have a solution that would allow me to detect any changes without shifting the focus off the input field
forget that last comment, your answer did mention this Unlike the input event, the change event so I switched @change="updateCurrentLevel" to @input="updateCurrentLevel"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.