bootstrap-vue formatter is changing input position - vue.js

I would like to live replace whitespaces with underscore _ in file name input in bootstrap-vue. But if I add a white space not at the end of input then formatter will move the cursor position to end of input. How to replace whitespaces without changing cursor position?
I tried:
<div class="form-group row">
<label class="col-6 col-md-4 col-lg-3 col-xl-3">File name</label>
<div class="col-6 col-md-8 col-lg-9 col-xl-9">
<b-input-group v-if="viewModel.offer">
<b-form-input ref="fileName" type="text" :formatter="formatter"></b-form-input>
<b-input-group-append>
<b-button variant="dark" v-b-popover.hover.top="'Download'" v-on:click=".."><i class="fas fa-arrow-circle-down"></i></b-button>
</b-input-group-append>
</b-input-group>
</div>
</div>
with:
formatter(value, event) {
const pos = event.target.selectionStart - 1
const nfileName = value.replace(/\s+/g, '_');
if (nfileName !== value) {
this.$nextTick(() => {
event.target.selectionEnd = pos
})
}
return nfileName;
}
but there the selectionStart value is equal selectionEnd, so that actual position is unknown.

Try using setTimeout instead of $nextTick.
setTimeout, should be run after the input has been allowed to render the new value (and moving the cursor).
I personally found this explaning pretty good, about how the logic functions.
http://www.hesselinkwebdesign.nl/2019/nexttick-vs-settimeout-in-vue/
new Vue({
el: '#app',
data() {
return {
text: ''
}
},
methods: {
formatter(value, event) {
const {
target: {
selectionEnd,
selectionStart
}
} = event
setTimeout(() => {
event.target.selectionStart = selectionStart;
event.target.selectionEnd = selectionEnd;
})
return value.replace(/\s+/g, '_');
}
}
})
<link href="https://unpkg.com/bootstrap#4.5.2/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://unpkg.com/bootstrap-vue#2.16.0/dist/bootstrap-vue.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.16.0/dist/bootstrap-vue.js"></script>
<div id="app">
<b-input v-model="text" :formatter="formatter"></b-input>
</div>

Related

vue.js - Add a placeholder in text box with click of a button

I have a textbox
<div>
<b-form-textarea
id="textarea"
v-model="text"
placeholder="Certification text "
rows="5"
max-rows="6"
></b-form-textarea>
</div>
And two buttons
<b-button variant="primary" class="btn btn-primary btn-lg top-right-button mr-1">Save</b-button>
<b-button variant="info" class="btn btn-info btn-lg top-right-button mr-1">Add Name</b-button>
When the user is typing and in between if he clicks Add Name button {name} should be placed in the textbox (Where ever the cursor is).
How can i implement it?
You can add a ref to your textarea, and access the selectionStart property, which contains the placement of the cursor.
You can then use this index to splice the given text into the textarea's text at the given position.
And since clicking the button will lose the focus of the input, you can add it back by calling the focus method on the ref, along with setting the selectionStart and selectionEnd so the cursor is where it left off.
new Vue({
el: "#app",
data() {
return {
text: ""
};
},
methods: {
addName() {
const { text } = this;
const textarea = this.$refs["my-textarea"].$el;
const index = textarea.selectionStart;
const name = "{name}";
this.text = `${text.substring(0, index)}${name}${text.substring(
index
)}`;
textarea.focus();
setTimeout(() => {
textarea.selectionStart = index + name.length;
textarea.selectionEnd = index + name.length;
});
}
}
});
<script src="https://unpkg.com/vue#2.6.12/dist/vue.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.17.3/dist/bootstrap-vue.js"></script>
<link href="https://unpkg.com/bootstrap#4.5.2/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://unpkg.com/bootstrap-vue#2.17.3/dist/bootstrap-vue.css" rel="stylesheet" />
<div id="app">
<b-form-textarea ref="my-textarea" v-model="text" placeholder="Certification text" rows="5" max-rows="6">
</b-form-textarea>
<b-btn variant="primary" size="lg" #click="addName">
Add Name
</b-btn>
</div>

Pagination for dynamically generated content

Consider the following code.
<template>
<div class="card-container">
<div class="row">
<div class="col s12">
<a #click="addCard()">Add Card</a>
</div>
</div>
<div class="row">
<div v-for="(card, index) in cards" :key="index">
<div class="card-panel">
<span class="card-title">Card Title</span>
<div class="card-action">
<a #click="deleteCard(index)">Delete</a>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data: function() {
return {
cards: []
}
},
methods: {
addCard: function() {
this.cards.push({
description: "",
});
},
deleteCard: function(index) {
this.cards.splice(index,1);
}
},
}
</script>
How to make the cards be grouped so that there are 4 rows and each row contains 4 cards? Upon reaching the fourth row the new cards go to the next page.
I thought I could use something like this codepen.io/parths267/pen/bXbWVv
But I have no idea how to get these cards organized in a pagination system.
The view would look something like this
My solution is calculate all cards of current page ahead.
Uses computed property to calculate the relate values which the pagination needs.
In below simple example (it is only one example, you need to add necessary validations as your actual needs, like boundary conditions) :
pages is the page count
cardsOfCurPage is the cards in current page
Then add one data property=pageIndex save the index of current page.
Anyway, keep data-driven in your mind.
List all arguments your pagination needs,
then declare them in data property or computed property.
execute the necessary calculations in computed property or methods.
PS: I don't know which css framework you uses, so I uses bootstrap instead.
Vue.component('v-cards', {
template: `<div class="card-container">
<div class="row">
<div class="col-12">
<a class="btn btn-danger" #click="addCard()">Add Card</a><span>Total Pages: {{pages}} Total Cards: {{cards.length}} Page Size:<input v-model="pageSize" placeholder="Page Size"></span>
</div>
</div>
<div class="row">
<div v-for="(card, index) in cardsOfCurrPage" :key="index" class="col-3">
<div class="card-panel">
<span class="card-title">Card Title: {{card.description}}</span>
<div class="card-action">
<a #click="deleteCard(index)">Delete</a>
</div>
</div>
</div>
</div>
<p><a class="badge" #click="gotoPrev()">Prev</a>- {{pageIndex + 1}} -<a class="badge" #click="gotoNext()">Next</a></p>
</div>`,
data: function() {
return {
cards: [],
pageSize: 6,
pageIndex: 0
}
},
computed: {
pages: function () {
return Math.floor(this.cards.length / this.pageSize) + 1
},
cardsOfCurrPage: function () {
return this.cards.slice(this.pageSize * this.pageIndex, this.pageSize * (this.pageIndex+1))
}
},
methods: {
addCard: function() {
this.cards.push({
description: this.cards.length,
});
},
deleteCard: function(index) {
this.cards.splice(index,1);
},
gotoPrev: function() {this.pageIndex -=1},
gotoNext: function() {this.pageIndex +=1}
},
})
new Vue({
el: '#app',
data() {
return {
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" rel="stylesheet" />
<div id="app">
<v-cards></v-cards>
</div>

Method Updating Data Twice on Form Submission

I have a component that is a form that I input a name and it updates a value on a field on the backend based on which input the name is. Right now I have two inputs (host and scout) and they work fine if I just fill one input. My problem is, when I fill both inputs, the name on the host input will always get updated twice while the name on the scout field will work just fine. Not sure if I was clear enough.
Here is the code for the component so far
<template>
<div class="add-wave">
<h3>Add Wave</h3>
<div class="row">
<form #click.prevent="addwave()" class="col s12">
<div class="row">
<div class="input-field col s12">
<input type="text" v-model="host" />
<label class="active">Host</label>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<input type="text" v-model="scout" />
<label class="active">Scout</label>
</div>
</div>
<button type="submit" class="btn">Submit</button>
<router-link to="/member" class="btn grey">Cancel</router-link>
</form>
</div>
</div>
</template>
<script>
import { db, fv } from "../data/firebaseInit";
export default {
data() {
return {
host: null,
scout: null
};
},
methods: {
addwave() {
this.addhost();
db.collection("members")
.where("name", "==", this.scout)
.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
doc.ref.update({
scout: fv.increment(1),
total: fv.increment(1)
});
});
});
},
addhost() {
db.collection("members")
.where("name", "==", this.host)
.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
doc.ref.update({
host: fv.increment(1),
total: fv.increment(1)
});
});
});
}
}
};
</script>
I'm not sure why is it updating twice only when I fill both input fields.

Vue.js adding a toggle and method to a button

I have a button that should toggle and also call a method. How do I achieve this? Seems like it can be only one or the other.
new Vue({
el: "#app",
data: {
iExist:false,
iDoNotExist: true,
},
methods: {
iSignedUpforThis: function(){
console.log("step X");
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<p v-show="iExist"> i EXISTS </p>
<p v-show="iDoNotExist">
<strong> You are not found: </strong>
<form >
First name:<br>
<input type="text" name="firstname" value="Mickey">
<br>
Last name:<br>
<input type="text" name="lastname" value="Mouse">
<br><br>
</form>
<BUTTON v-on:click="iExists = iDoNotExist">
TOGGLE MY EXISTENCE
</BUTTON>
</div>
Move
iExists = iDoNotExist to a method:
methods: {
iSignedUpforThis: function(){
this.iExist = this.iDoNotExist
console.log("step X");
}
}
<button v-on:click="iSignedUpForThis">
TOGGLE MY EXISTENCE
</button>
First off to accomplish your desired result you need only one Boolean variable. Then in your method just switch between true and false. Also you have an invalid markup - there is closing tap p but no closing. That's why your example does not work.
Notice: it's bad idea to nest form tag inside p tag, so use div instead. It's considered a good practice to associate your input with it's label using label tag. Also there is shortcut for v-on:click - #click. data should be an function that returns an object, this will prevent . multiple instance to share the same object.
If you follow above recommendations you will make your code much clear and bug-less.
new Vue({
el: '#app',
data: {
isExist: false,
},
methods: {
method() {
this.isExist = !this.isExist
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-show="isExist">I exist</div>
<div v-show="!isExist">
<strong>You are not found:</strong>
<form>
<label>First name:<br>
<input type="text" name="firstname" value="Mickey">
</label>
<br>
<label>Last name:<br>
<input type="text" name="lastname" value="Mouse">
</label>
</form>
</div>
<button #click="method">Toggle</button>
</div>
It might be late but I am sure it will help others. Create a component ToggleButton.js and paste the below codes.
<template>
<label for="toggle_button">
<span v-if="isActive" class="toggle__label">On</span>
<span v-if="! isActive" class="toggle__label">Off</span>
<input type="checkbox" id="toggle_button" v-model="checkedValue">
<span class="toggle__switch"></span>
</label>
</template>
<script>
export default {
data() {
return {
currentState: false
}
},
computed: {
isActive() {
return this.currentState;
},
checkedValue: {
get() {
return this.defaultState
},
set(newValue) {
this.currentState = newValue;
}
}
}
}
</script>
Take a look at the article to learn more https://webomnizz.com/create-toggle-switch-button-with-vue-js/

v-on:click not working in a child component

First thing first I'm new to vue.js.
what I'm trying to do when the user click on the expander anchor tag in the item-preview component the item-details will display and the item-preview will be hide. Now the problem occurs when the item-preview displayed and i'm trying to toggle it by clicking its own expander anchor tag. I do not whats wrong with this.
Here is my HTML templates.
<script type="text/x-template" id="grid">
<div class="model item" v-for="entry in data">
<item-preview v-bind:entry="entry" v-if="entry.hide == 0">
</item-preview>
<item-details v-bind:entry="entry" v-if="entry.hide == 1">
</item-details>
</div>
</script>
<script type="text/x-template" id="item-preview">
<header class="preview">
<a class="expander" tabindex="-1" v-on:click="toggle(entry)"></a>
<span class="name rds_markup">
{{ entry.name }}
</span>
</header>
</script>
<script type="text/x-template" id="item-details">
<div class="edit details">
<section class="edit" tabindex="-1">
<form action="#">
<fieldset class="item name">
<a class="expander" v-on:click="toggle(entry)"></a>
{{ entry.name }}
</fieldset>
</form>
</section>
</div>
</script>
And here how I called the grid component on my view.
<grid
:data="packages">
</grid>
And for my Vue implementation
var itemPreview = Vue.component('item-preview',{
'template':"#item-preview",
'props':{
entry:Object
},
methods:{
toggle:function(entry){
entry.hide = !!entry.hide;
return true;
}
}
});
var itemDetails = Vue.component('item-details',{
'template':"#item-details",
'props':{
entry:Object
},
methods:{
toggle:function(entry){
entry.hide = !!entry.hide;
return true;
}
}
});
var grid = Vue.component('grid',{
'template':"#grid",
'props':{
data:Array,
},
components:{
'item-preview': itemPreview,
'item-details': itemDetails
},
methods:{
toggle:function(entry){
entry.hide = !!entry.hide;
return true;
}
}
});
var vm = new Vue({
el:'#app',
data:{
message:'Hello',
packages:{}
},
ready:function(){
this.fetchPackages();
},
methods:{
fetchPackages:function(){
this.$http.get(url1,function( response ){
this.$set('packages',response);
});
},
}
});
Silly me. It took me 30minutes to figure out what is wrong with this code.
What I did to fix this is instead of entry.hide = !!entry.hide; I use entry.hide = true in item-preview component and in the item-details entry.hide = false. this fix my issue.