Home » vue » Binding Vue.js to all instances of an element, without(?) using Components

Binding Vue.js to all instances of an element, without(?) using Components

Posted by: admin November 26, 2021 Leave a comment

Questions:

Today I’m learning Vue.js, and I have a few ideas of where it might be really useful in a new project that’s an off-shoot of an existing, live project.

I like the idea of trying to replace some of my existing functionality with Vue, and I see that Components may be quite handy as quite a lot of functionality is re-used (e.g. Postcode lookups).

Once of the pieces of functionality I’ve used for an age is for invalid form elements – currently in jQuery when a form input or textarea is blurred I add a class of form__blurred, and that is coupled with some Sass such as:

.form__blurred {
    &:not(:focus):invalid {
        border-color:$danger;
    }
}

This is to avoid styling all required inputs as errors immediately on page load.

I’m totally fine with doing this in jQuery, but I figured maybe it could be done in Vue.

I have an idea of how I might do it with components thanks to the laracasts series, but my form inputs are all rendered by Blade based on data received from Laravel and it doesn’t seem like a neat solution to have some of the inputs rendered in Javascript, for a number of reasons (no JS, confusion about where to find input templates, etc).

I figured something like the following simplified example would be handy

<input type="text" class="form__text" v-on:blur="blurred" v-bind:class="{ form__blurred : isBlurred }" />

<script>
    var form = new Vue({
        el : '.form__input',
        data : {
            isBlurred : false
        },
        methods : {
            blurred : function() {
                this.isBlurred = true;
            }
        }
    });
</script>

That actually works great but, as expected, it seems like using a class selector only selects the first instance, and even if it didn’t, I’m guessing changing the properties would apply to all elements, not each one individually.

So the question is – is this possible with pre-rendered HTML, in a way that’s smarter than just looping through a selector?

If it is, is there a way to create the Vue on a .form element and apply this function to both .form__input and .form__textarea?

Or, as is probably the case, is this just not a good use-case for Vue (since this is actually a lot more code than the jQuery solution).

Answers:

Sounds like a great use case for a Custom Directive.

Vue allows you to register your own custom directives. Note that in Vue 2.0, the primary form of code reuse and abstraction is components – however there may be cases where you just need some low-level DOM access on plain elements, and this is where custom directives would still be useful.

<div id="app">
    <input type="text" name="myforminput" v-my-directive>
</div>

<script>
    Vue.directive('my-directive', {
        bind: function (el) {
            el.onblur = function () {
                el.classList.add('form__blurred');
            }
        }
    });

    var app = new Vue({
        el: '#app'
    });
</script>

You can also add the directive locally to a parent component, if it makes sense for your application.