vue 3.0 can mount multiple elements used class name? - vue.js

i want make formatted text input like below:
<input type="text" class="number-format" v-model="numberModel">
...
and mount vue that elements:
const numberInput = {
computed: {
numberModel: {
get: function() {
return new Intl.NumberFormat().format(this.value);
},
set: function(n) {
this.value = parseInt(n.replaceAll(",", ""));
}
}
},
data: function() {
return {
value: ""
}
}
}
Vue.createApp(numberInput).mount(".number-format");
but it mounted only first element of .number-format.
i want make vue root app like component(but, it is not component).
<number-format-input ........> (x)
<input type="text" ....> (o)
any possible solutions?

mount() expects its argument to resolve to a single DOM node. It can be either the DOM node itself or a CSS selector - but if multiple nodes match the CSS selector, only the first node will be replaced with the generated Vue instance.
You will need to first register a global Vue component - and then use that component in your template, presumably with v-for directive to define multiple instances of that component.

Related

Is there a way to pass the returning value of a method as a prop to a Vue component?

I have a Vue.js component (named <dynamic-input/>) which accepts a prop (called input). I'm trying to pass the returning value of a method (named normalizeInput) which is defined in the parent component:
template
<div v-for="input in inputList" :key="input.id">
<dynamic-input :input="normalizeInput(input)" />
</div>
script
methods: {
normalizeInput(input) {
//do something with input
return normalizedInput;
}
}
Apparently this doesn't work; Is there a way to achieve this? Am I Doing Something Wrong?
I'm Using nuxt v2.15.7
You've to use a computed property that returns a function with the input as parameters :
computed: {
normalizeInput() {
return (input) =>{
return normalizedInput;
}
}
}

How to avoid using Vue.set() within external js class files preserving reactivity for nested object properties?

I have this component:
<template>
<div class="simple-editor">
{{editor.view.toolbarManager.buttons}}
<component
v-for="(button, name) in editor.view.toolbarManager.buttons"
:is="button.component"
:options="button.options"
:key="name"
></component></div>
//.......................
I am trying to use editor.view.toolbarManager.buttons within v-for loop. Vue devtools shows me (for all 3 cases bellow) that editor.view.toolbarManager.buttons is an Object and contains 4 properties which contains another object.
<script>
export default {
data: function() {
return {
editor: new Editor({
doc: this.value,
init: this.init,
}),
}
},
editor.view.toolbarManager.buttons is filling in within subclasses of Editor() class with dynamically imported scripts like this:
props.plugins.forEach(plugin => {
this.plugins[plugin] = import(/* webpackMode: "eager" */ '../plugin/' + plugin);
});
I fill in editor.view.toolbarManager.buttons like this:
// case 1: works fine as expected
Vue.set(this.buttons, name, {
component,
options,
});
/* case 2: loses vue reactivity
var button = {};
button[name] = {
component,
options,
};
Object.assign(this.buttons, button);
*/
/* case 3: loses vue reactivity
this.buttons[name] = {
component,
options,
};
*/
The issue is next: when I try to render {{editor.view.toolbarManager.buttons}} within template I see empty object for cases 2 and 3 like this:
{}
which means vue reactivity is broken. Editor() is external class and I don't want to tie it to Vue. Vue reactivity is fine for within external classes for arrays because of using splice/push methods. Does exist a similar methods for object properties with preserving Vue reactivity?
Oh! I have messed up with Object.assign(). This is a right using of Object.assign() instead of Vue.set():
var button = {};
button[name] = {
component,
options,
};
this.buttons = Object.assign({}, this.buttons, button);
This works fine for me. And documentation is here https://v2.vuejs.org/v2/guide/reactivity.html#For-Objects :
// instead of `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })

How can I use the width of an element in my computed property reactively

I am attempting to set a computed property based on the width of an element in my component. The problem is that I do not know how to get the width in a way that is reactive.
I've got a ref on my element to get the width which works. However I can't seem to get vue to detect that the width is changing
I have the element set up as:
<div ref="myDiv"></div>
and my computed property as:
myProperty() {
return this.$refs.myDiv.clientWidth/2;
}
myProperty evaluates correctly, but does not change as the width of myDiv changes
You can listen to resize event
data() {
return {
clientWidth: 0
}
},
// bind event handlers to the `handleResize` method (defined below)
mounted: function () {
window.addEventListener('resize', this.handleResize)
},
beforeDestroy: function () {
window.removeEventListener('resize', this.handleResize)
},
methods: {
// whenever the document is resized, re-set the 'clientWidth' variable
handleResize (event) {
if (this.$refs.myDiv) {
this.clientWidth = this.$refs.myDiv.clientWidth
}
}
}
then you can use this.clientWidth where you want to get clientWidth.
myProperty() {
return this.clientWidth/2;
}
Another option for future readers of this question is to use a simple mixin I created https://github.com/Circuit8/vue-computed-dimensions. It creates computed properties for the dimensions and position of any ref you like, as in the example here:
<template>
<div ref="wrapper">
...
<div ref="other">...</div>
</div>
</template>
<script>
import computedDimensions from "vue-computed-dimensions";
export default {
// computedDimensions accepts a list of refs to use.
// each ref provided will produce 4 computed properties
// in this example we will have wrapperWidth wrapperHeight wrapperX and wrapperY. As well as otherWidth, otherHeight, otherX, and otherY.
// these can then be used in other computed properties to base reactivity on the rendered dimensions of an element
mixins: [computedDimensions("wrapper", "other")],
};
</script>

Vuelidate: validate form with sub components

How to work with validations of nested components inside a parent component with Vuelidate? I would like to change parentForm.$invalid if inputs in subcomponents are valid or not.
Parent:
<parent-component>
</child-component-1>
</child-component-2>
</parent-component>
validations: {
parent: WHAT HERE?
}
Child-1
<child-component-1>
</some-input>
</child-component-1>
data() {
return {
someInput: ""
};
},
validations: {
someInput: required
}
Child-2
<child-component-2>
</some-input>
</child-component-2>
data() {
return {
someInput: ""
};
},
validations: {
someInput: required
}
I might not be an expert in Vue.
If you have declared validations in the child component and you want to access it from the parent component you can use reference the child component from parent component in this way.
In parent component it would be like
<template>
<my-child ref="mychild"> </my-child>
</template>
You can access the validations declared in my-child component which is $v object using
this.$refs.mychild.$v
and then you can use validations of child component in parent components with such ease. Hope this will make the job much easier then using complex ways and it worked for me.
The simplest way to get started with vuelidate for sub-components/form is to use Vue.js dependency injection mechanism provided by provide/inject pair. The $v instance created in parent component can be shared with children component.
As you more fine tune it, you can use Vuelidate data-nesting and only pass a subset of $v to your subcomponents. This is a roughly similar approach to how Angular does with nested Forms. It would look something like:
export default {
data() {
return {
form1: {
nestedA: '',
nestedB: ''
} /* Remaining fields */
}
},
validations: {
form1: {
nestedA: {
required
},
nestedB: {
required
}
},
form2: {
nestedA: {
required
},
nestedB: {
required
}
}
}
}
Alternately, you can declare independent instances of $v for each component. In your case, you will have one for parent and two for children. When you hit the submit button, get the reference of child component using $refs and check if nested form within the child component is valid or not.

Vue.js How to output attribute inside html tag when attribute name is included in value?

If I have a data structure that contains field attributes as follows, how can I output the dataAttributes value inside the html?
var app3 = new Vue({
el: '#app-3',
data: {
field: {
type: 'text,
name: 'First Name',
class: 'form-control js-user-lookup',
dataAttributes: 'data-autocomplete-url=api/users data-selected=123',
}
}
})
<input :type="field.type"
:id="field.name"
:name="field.name"
:class="field.class"
{{field.dataAttributes}} />
You can't use the mustache syntax inside of html tags and I cant bind it to a data-* attribute since the attribute is part of the value e.g. data-autocomplete-url and data-selected?
You cannot do that with plain data-binding syntax. You will need to use the custom directive. It will look like this.
<input :name="field.name" v-data-binder="field.dataAttributes" />
Your directive definition will be something like:
// Register a global custom directive called `v-focus`
Vue.directive('data-binder', {
// When the bound element is inserted into the DOM...
inserted: function (el, binding) {
// Perform data attribute manipulations here.
// 1. Parse the string into proper key-value object
// binding.value
// 2. Iterate over the keys of parsed object
// 3. Use JavaScript *dataset* property to set values
}
})
You will also need updated hook in your directive definition to remove existing data-* attributes whenever the value passed to the directive changes.
You can more easily do it if you have the dataAttributes string passed as a javascript object and just bind it like that v-bind="myobject". If not possible you can transform it via a computed property
Check below example
<div id="app">
</div>
var instance = new Vue({
el:'#app',
data:function(){
return {
inputType:'password',
fieldAttributes:"data-autocomplete-url=api/users data-selected=123"
};
},
computed:{
getDataAttributes:function(){
var attributes = this.fieldAttributes.split(' ');
var attributesO = {};
for(var a=0;a<attributes.length;a++){
var attribute = attributes[a];
var attribute_ar = attribute.split('=');
attributesO[attribute_ar[0]] = attribute_ar[1];
}
return attributesO;
}
},
methods:{
getAttribute:function(){
alert(this.$refs.myinput.dataset.selected)
}
},
template:`<div>
<input ref="myinput" v-on:click="getAttribute" :type="inputType" v-bind="getDataAttributes" />
</div>`
});