How to get selectionStart and selectionEnd properties of v-textarea in VueJS? - vue.js

I know you can get the startSelection and selectionEnd properties of normal <textarea> by using DOM or $refs in VueJS.
I however am struggling to accomplish the same thing with <v-textarea>. I have googled around and most things either use normal JS with <textarea> or VueJS but also with <textarea>.
Sample Code:
<v-textarea
:value="value"
:label="label"
hide-details="auto"
:name="name"
auto-grow
no-resize
clearable
rows="1"
#input="emitSelectedPositons"
:error-messages="errorMessages"
ref="vta"
/>
...
<script>
....
methods: {
emitSelectedPositons() {
this.$emit('changed', {
start: this.$refs.vta.selectionStart,
end: this.$refs.vta.selectionEnd
})
}
}
</script>
The code above returns undefined for both.

$refs.vta only returns the v-textarea element, which is a wrapper element containing its own refs which includes the inner <textarea> input element. You can access the inner element to get selectionStart and selectionEnd like so:
this.start = this.$refs.vta.$refs.input.selectionStart;
this.end = this.$refs.vta.$refs.input.selectionEnd;
sandbox example

Related

Conditional wrapper rendering in vue

I'm making a link/button component which either can have a button or an anchor wrapper, a text and an optional icon. My template code below is currently rendering either an anchor or a button (with the exact same content) based on an if statement on the wrapper element, resulting in duplicate code.
<template>
<a v-if="link" v-bind:href="url" class="btn" :class="modifier" :id="id" role="button" :disabled="disabled">
{{buttonText}}
<svg class="icon" v-if="icon" :class="iconModifier">
<use v-bind="{ 'xlink:href':'#sprite-' + icon }"></use>
</svg>
</a>
<button v-else type="button" class="btn" :class="modifier" :id="id" :disabled="disabled">
{{buttonText}}
<svg class="icon" v-if="icon" :class="iconModifier">
<use v-bind="{ 'xlink:href':'#sprite-' + icon }"></use>
</svg>
</button>
</template>
Is there a more clean way for wrapping my buttonText and icon inside either an anchor or button?
I've solved my issue by intensive Google-ing! Found this issue regarding Vue on Github which pointed me in the right direction.
Small piece of backstory
I'm using Vue in combination with Storybook to build a component library in which a button can either be a button or an anchor. All buttons look alike (apart from color) and can be used for submitting or linking. To keep my folder structure ordered, I would like a solution that generates a multiple buttons types (with or without link) from one single file.
Solution
Using computed properties I'm able to "calculate" the necessary tag, based on the url property of my component. When a url is passed, I know that my button has to link to another page. If there is no url property it should submit something or preform a custom click handler (not in the sample code below).
I've created the returnComponentTag computed property to avoid placing any complex or bulky logic (like my original solution) in my template. This returns either an a or a button tag based on the existence of the url property.
Next, as suggested by ajobi, using the :is attribute I'm able to define the component tag based on the result of my computed property. Below a stripped sample of my final (and working) solution:
<template>
<component :is="returnComponentTag" v-bind:href="url ? url : ''" class="btn" :class="modifier" :id="id">
{{buttonText}}
</component>
</template>
<script>
export default {
name: "Button",
props: {
id: {
type: Number
},
buttonText: {
type: String,
required: true,
default: "Button"
},
modifier: {
type: String,
default: "btn-cta-01"
},
url: {
type: String,
default: ""
}
},
computed: {
returnComponentTag() {
return this.url ? "a" : "button"
}
}
};
</script>
You could extract the wrapping element into a dedicated component.
<template>
<a v-if="link" v-bind:href="url" class="btn" :class="modifier" :id="id" role="button" :disabled="disabled">
<slot></slot>
</a>
<button v-else type="button" class="btn" :class="modifier" :id="id" :disabled="disabled">
<slot></slot>
</button>
</template>
// You would use it like this
<SomeComponent /* your props here */ >
{{buttonText}}
<svg class="icon" v-if="icon" :class="iconModifier">
<use v-bind="{ 'xlink:href':'#sprite-' + icon }"></use>
</svg>
</SomeComponent>
There are multiple ways of doing this. Two examples would be the following based on the point of view:
You are defining two different components (Button or Anchor) and want to use a wrapper to render either one of them.
You could seperate the Wrapper Content into two components so that the wrapper only decides on which of the components to render (either the Button or the Anchor).
The problem with this approach could be you will have doubled code for methods and styling for the button and anchor component.
You are defining the content as a component and use the wrapper to define what to wrap the content in.
See Answer of https://stackoverflow.com/a/60052780/11930769
It would be great to know, why you would want to achive this. Maybe there are better solutions for your usecase. Cheers!

Vue mounted jquery code not working on DOM re-render

I'm trying to use jqueryui datapicker with vue. The input for the datepicker is inside a template if-else condition.
The datepicker works just fine initially but after flipping back and forth using the template if-else it stops working.
It seems the jquery code is no longer mounted after a DOM re-render.
<template v-if="...">
<input name="startdate" id="startdate" v-model="date" type="text">
</template>
<template v-else>
<div>Some other display</div>
</template>
mounted: function() {
$('#startdate').datepicker({
onSelect:function(selectedDate, datePicker) {
this.date = selectedDate;
}
});
I placed the jquery code instead inside "updated" and it works now, but this code gets called every single time there is a DOM change.
I'm wondering it there is a better way to accomplish this?
updated: function () {
this.$nextTick(function () {
....
....
You might use v-show instead. This will only toggle the display value of the element, not remove from or add to the DOM, so any event handlers attached won't be affected. From the Vue docs: https://v2.vuejs.org/v2/guide/conditional.html#v-show
That said, #Imre_G is probably right about finding a Vue datepicker to use.
Try this: use your component with mouted() hook and just add to your component the v-once directive. Like this: <input name="start date" ... v-once>
But, as mentioned in Imre_G comment, consider to use some premaded Vue datepicker component. It is far more better idea as mixing Vue with jQuery.

VueJS - pass properties from one component to another in the template

I'm new to Vue, and am trying to build up a set of business components that rely on other lower-level components, and pass properties to them. None of the usual data-binding v-bind:propName="xxx" or :propName="xxx" seem to work from inside a component template however, and all the documentation treats prop passing from a top-level HTML page instead of from one component to another.
For example, with the below I have a lower-level component called "exchange-panel" which takes a "title" property. I want my higher-level component to use that generic exchange-panel and pass in a title to use. I get the following error:
[Vue warn]: Error compiling template:
<exchange-panel :title="The Panel Title">
<h1>test</h1>
</exchange-panel>
- invalid expression: :title="The Panel Title"
found in
---> <OrderbookDetail>
Here is the sample code (also at https://jsfiddle.net/ns1km8fh/
Vue.component("orderbook-detail", {
template:`
<exchange-panel :title="Public order book">
<h1>test</h1>
</exchange-panel>
`
});
Vue.component("exchange-panel", {
template:`
<div class="panel panel-default">
<div class="panel-heading">Title: {{ title }}</div>
<div class="panel-body">
<slot></slot>
</div>
</div>`,
props: ["title"]
})
new Vue({
el: "#exchange-container",
created: function () {
window.Vue = this
}
})
You need to quote Public order book.
<exchange-panel :title="'Public order book'">
The value of a bound attribute is treated as a javascript expression. Javascript strings need to be enclosed in quotes of some kind (single, double, backticks).
Updated fiddle.
As #RoyJ points out, you can also simply not bind it and use it as a normal attribute.
<exchange-panel title="Public order book">

Keeping things DRY in Vue.js

I've done a couple of projects with React in the last year and I've switched to Vue for my current one, attracted by its greater simplicity, less verbose nature and the fact that you don't have to transpile your code to work, so it's easier to get going with and more flexible (well, to be accurate you don't have to transpile with React either, there's no need to use JSX, but it loses one of its great benefits if you don't).
Anyway, one of the things I'm missing from React (and I'm sure it's just ignorance of the Vue way which is my problem) is a way of reusing a fragment of code to avoid repeating myself in templates. The specific situation which prompted this question was a template where I have a custom input element like this:
<input ref="input" :id='name' :name='name' :type='fieldType' class='form-control' :value="value" :readonly="readonly" :disabled="disabled" #input="handleInput"/>
In certain situations I'd want to wrap it in a div, otherwise I'd want to use it as is. With React, I'd simply store it in a variable, something like this:
var inp=( <input ref="input" :id='name' :name='name' :type='fieldType' class='form-control' :value="value" :readonly="readonly" :disabled="disabled"
#input="handleInput"/>);
Then I could do something like the following:
var myInput;
if(divSituation){
myInput=(<div>{inp}</div>);
} else {
myInput=inp;
}
Then I could use the myInput var. The Vue logic doesn't seem to allow this, though. Unless, of course, using JSX within Vue would allow me to do the very same thing? I currently have for this in Vue something like the following, which offends me:
<template v-if="divSituation">
<div><input ref="input" :id='name' :name='name' :type='fieldType' class='form-control' :value="value" :readonly="readonly" :disabled="disabled" #input="handleInput"/></div>
</template>
<template v-else>
<input ref="input" :id='name' :name='name' :type='fieldType' class='form-control' :value="value" :readonly="readonly" :disabled="disabled" #input="handleInput"/
</template>
You can create vue components for re-usable components, which can be used as par requirement.
You can find an example of re-usable input component in vue docs:
<currency-input v-model="price"></currency-input>
and you can write that as re-usable component like following:
Vue.component('currency-input', {
template: '\
<span>\
$\
<input\
ref="input"\
v-bind:value="value"\
v-on:input="updateValue($event.target.value)"\
>\
</span>\
',
props: ['value'],
methods: {
// Instead of updating the value directly, this
// method is used to format and place constraints
// on the input's value
updateValue: function (value) {
var formattedValue = value
// Remove whitespace on either side
.trim()
// Shorten to 2 decimal places
.slice(0, value.indexOf('.') + 3)
// If the value was not already normalized,
// manually override it to conform
if (formattedValue !== value) {
this.$refs.input.value = formattedValue
}
// Emit the number value through the input event
this.$emit('input', Number(formattedValue))
}
}
})
You can add more props for readonly, disabled, etc.
You can also have a look at custom input elements of element-ui and it's code.
Given the example you have given, You can use v-html more efficiently. with v-html, you can pass a HTML string which will be rendered as HTML. However Note: the contents are inserted as plain HTML - they will not be compiled as Vue templates.
You can have a computed property, which will return HTML string as par your variable: divSituation, like following:
var data = {
templateInput: '<input ref="input" :id="name" :name="name" :type="fieldType" class="form-control" :value="value" :readonly="readonly" :disabled="disabled" #input="handleInput"/>',
divSituation: true,
myInput: ''
}
var demo = new Vue({
el: '#demo',
data: function(){
return data
},
computed: {
getMyInput: function(){
if(this.divSituation){
return this.templateInput
}
else{
return '<div>' + this.templateInput + '</div>'
}
}
}
})
Now you can just render myInput in HTML using v-html like this:
<div id="demo">
<div v-html="getMyInput">
</div>
</div>
check out working fiddle.

Using $refs with Element UI's input component

Is it possible to use ref with el-input component from Element-UI? I am trying to use $refsto focus on the input when my Vue instance is mounted. Here is my code:
<div id="app">
<el-input type="text" ref="test" placeholder="enter text"></el-input>
</div>
And in my Vue instance:
new Vue({
el: "#app",
mounted(){
this.$refs.test.focus()
}
})
The focus method is not working at all, even if I move this.$refs.test.focus() into a method and try to trigger it through an event.
The $refs object stores Vue components and should be working fine. The problem is that you are attempting to invoke a focus method which doesn't exist in the component, but rather on an input somewhere inside the component's template.
So, to actually find the input you'd have to do something like this:
this.$refs.test.$el.getElementsByTagName('input')[0].focus();
Not the prettiest line of code ever made, right? So, instead of calling anything in your app's mounted method, if you want to autofocus on the input, just do this:
<el-input type="text" placeholder="enter text" autofocus></el-input>
This can be also solved your problem.
// Focus the component, but we have to wait
// so that it will be showing first.
this.$nextTick(() => {
this.$refs.inputText.focus();
});
Now you can use it like this
<div id="app">
<el-input type="text" ref="test" placeholder="enter text"></el-input>
</div>
this.$refs.test.focus();
https://element.eleme.io/#/en-US/component/input#input-methods