How can I block some characters in a textarea with VueJs? - javascript

I have a textarea for sending messages and I wanna block emails and site links. So when I write an # or https:// there must be error messages shown, using v-if, but how can I do this? Which functions?
new Vue({
el: "#app",
data: {
message: {
content: ""
}
},
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div>
<textarea v-bind="message" v-model="message.content" cols="30" rows="10">
</textarea>
<p v-if="message.content == '#'">
No special characters
</p>
</div>
</div>

You can check by regex type , for your condition var regex = /(#|https)/g; . Also set hasError data for message display control and you can use vue watch for your data changing (message.content)
new Vue({
el: "#app",
data: {
message: {
content: ""
},
hasError: false
},
watch: {
'message.content': function(newVal,oldVal) {
var regex = /(#|https)/g;
this.hasError = newVal.match(regex);
}
}
})
body {
background: #20262E;
padding: 10px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 10px;
transition: all 0.2s;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div>
<textarea v-bind="message" v-model="message.content" cols="30" rows="5">
</textarea>
<p v-if="hasError">
No special characters
</p>
</div>
</div>

i think you should add method #change or #input to textarea, and then test for everything using regex and than replace it or whatever you want to do

Related

TypeError: this.$refs is not a function

So I have a problem with VueJs. I created a "Confirmation Dialogue" and added it to a On-Click-Event on buttons. It worked fine.
Now I tried to copy the implementation to add it to another button on a different parent. It says "TypeError: this.$refs.confirmDialogue.show is not a function" in the Console whenever I try to click the button. The other button still works completly normal.
Am I missing something? I already tried to remove the working button, so only one component uses the Confirm Dialogue but that also didn't work.
I'm new to VueJs. Hope someone can help me with this problem.
Child PopupModal:
<template>
<transition name="fade">
<div class="popup-modal" v-if="isVisible">
<div class="window">
<slot></slot>
</div>
</div>
</transition>
</template>
<script>
export default {
name: 'PopupModal',
data: () => ({
isVisible: false,
}),
methods: {
open() {
this.isVisible = true
},
close() {
this.isVisible = false
},
},
}
</script>
<style scoped>
/* css class for the transition */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.popup-modal {
background-color: rgba(0, 0, 0, 0.5);
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
padding: 0.5rem;
display: flex;
align-items: center;
z-index: 1;
}
.window {
background: #fff;
border-radius: 5px;
box-shadow: 2px 4px 8px rgba(0, 0, 0, 0.2);
max-width: 480px;
margin-left: auto;
margin-right: auto;
padding: 1rem;
}
</style>
Parent ConfirmDialogue:
<template>
<popup-modal ref="popup">
<h2 style="margin-top: 0">{{ title }}</h2>
<p>{{ message }}</p>
<div class="btns">
<button class="cancel-btn" #click="_cancel">{{ cancelButton }}</button>
<span class="ok-btn" #click="_confirm">{{ okButton }}</span>
</div>
</popup-modal>
</template>
<script>
import PopupModal from "../confirmDialogue/PopupModal.vue"
export default {
name: 'ConfirmDialogue',
components: { PopupModal },
data: () => ({
// Parameters that change depending on the type of dialogue
title: undefined,
message: undefined, // Main text content
okButton: undefined, // Text for confirm button; leave it empty because we don't know what we're using it for
cancelButton: 'Abbrechen', // text for cancel button
// Private variables
resolvePromise: undefined,
rejectPromise: undefined,
}),
methods: {
show(opts = {}) {
this.title = opts.title
this.message = opts.message
this.okButton = opts.okButton
if (opts.cancelButton) {
this.cancelButton = opts.cancelButton
}
// Once we set our config, we tell the popup modal to open
this.$refs.popup.open()
// Return promise so the caller can get results
return new Promise((resolve, reject) => {
this.resolvePromise = resolve
this.rejectPromise = reject
})
},
_confirm() {
this.$refs.popup.close()
this.resolvePromise(true)
},
_cancel() {
this.$refs.popup.close()
this.resolvePromise(false)
// Or you can throw an error
// this.rejectPromise(new Error('User cancelled the dialogue'))
},
},
}
</script>
<style scoped>
.btns {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.ok-btn {
padding: 0.5em 1em;
background-color: #1F51FF;
color: #fff;
border: 2px solid #0ec5a4;
border-radius: 5px;
font-size: 16px;
text-transform: uppercase;
cursor: pointer;
}
.cancel-btn {
padding: 0.5em 1em;
background-color: #d5eae7;
color: #000;
border: 2px solid #0ec5a4;
border-radius: 5px;
font-size: 16px;
text-transform: uppercase;
cursor: pointer;
}
</style>
Working button:
<td class="last-td last-row">
<div class="button-wrapper">
<div class="wrapper-edit">
<button class="button button-edit">Bearbeiten</button>
</div>
<div class="wrapper-cancel">
<button class="button button-cancel" #click="doDelete">Löschen</button> <!-- Here is the working button -->
<confirm-dialogue ref="confirmDialogue"></confirm-dialogue>
</div>
</div>
</td>
</tr>
</thead>
</template>
<script>
import ConfirmDialogue from '../confirmDialogue/ConfirmDialogue.vue'
export default {
name: "bookingElements",
components: { ConfirmDialogue },
methods: {
async doDelete() {
const ok = await this.$refs.confirmDialogue.show({
title: 'Buchung löschen',
message: 'Sind Sie sicher, dass Sie die Buchung löschen wollen?',
okButton: 'Buchung löschen',
})
if (ok) {
alert('Die Buchung wurde Erfolgreich gelöscht')
}
},
Button that isn't working:
<td id="buttonCell">
<button class="button" #click="doDelete">Buchen</button>
<confirm-dialogue ref="confirmDialogue"></confirm-dialogue>
</td>
</tr>
</tbody>
</template>
<script>
import ConfirmDialogue from "../confirmDialogue/ConfirmDialogue.vue"
export default {
name: "bookElements",
components: { ConfirmDialogue },
methods: {
async doDelete() {
const ok = await this.$refs.confirmDialogue.show({
title: 'Buchung löschen',
message: 'Sind Sie sicher, dass Sie die Buchung löschen wollen?',
okButton: 'Buchung löschen',
})
if (ok) {
alert('Die Buchung wurde Erfolgreich gelöscht')
}
},
I got found the mistake.
I created a for-each loop to create the Table.
At the working button there was no for-each loop though.
So VueJs tried to generate the Confirmation Dialogue 9 times and this resulted to an error.
So I just need to put the
<confirm-dialogue ref="confirmDialogue"></confirm-dialogue>
to the top of Template like this
<template>
<confirm-dialogue ref="confirmDialogue"></confirm-dialogue>
...
</template>
because it's vanished and only shows when the button is clicked.

How to add class from content of a variable in Vue.js

I want to pass a variable to a component which should be added as a class to a div. However, Vue adds the name of the variable instead of the content.
So I pass the prop color which contains red.
What I want: <div class="red">2</div>
What I get: <div class="color">2</div>
I think this is a noob question, so sorry for that. Maybe there is a better way to do this.
Thanks for helping me out.
Here are my components:
<template>
<div class="btn btn-outline-primary cell"
:class="{color}"
:id="id">
{{ number }}
</div>
</template>
<script>
export default {
name: "TileElement",
props: ["id", "number", "color"]
}
</script>
<style scoped>
.green {
color: green;
}
.red {
color: red;
}
.yellow {
color: yellow;
}
.cell {
display: inline-block;
float: left;
margin: 0.1em;
text-align: center;
background-color: lightgray;
width: 2.7em;
height: 2.7em;
}
</style>
Parent Component:
<template>
<div class="row">
<TileElement
v-for="tile in tiles"
v-bind:key="tile.id"
v-bind:number="tile.number"
v-bind:color="tile.color"
></TileElement>
</div>
</template>
<script>
import TileElement from './TileElement.vue';
export default {
name: "TileRow",
components: {
TileElement
},
data: function () {
return {
tiles: [
{id: 1, number: 1, color: "green"},
{id: 2, number: 2, color: "red"},
{id: 3, number: 3, color: "yellos"}
]
}
}
}
</script>
If you just need to pass a class, without any conditions or other such stuff, then you can simple use array for one and any other number of classes:
<div :class="[color]"></div>
But that's not only you can do.
https://v2.vuejs.org/v2/guide/class-and-style.html
:class="color" will also work:
var vm = new Vue({
el: '#app',
data() {
return {
color: 'red'
};
}
});
.cell { width: 50px; height: 50px; }
.red { background: red; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.21/vue.min.js"></script>
<div id="app">
<div class="cell" :class="color"></div>
</div>
Try dynamically importing the class name
:class="{[color]: true}"

How to short-circuit v-model in custom directives?

I want to have a custom directive I can attach to any <input> which will limit it to alpha chars only. Here's what I have so far:
https://jsfiddle.net/m473tpfu/
It works, however, the Vue.js docs state:
Apart from el, you should treat these arguments as read-only and never modify them.
So modifying vnode is a bad pattern. How can I achieve this differently?
You could do it like this: your directive has an input handler that checks whether forbidden chars are included, and if so, strips them out and issues a new input Event so that everything updates.
The usual issue of cursor moving to the end of the field when you rewrite its value applies.
Vue.directive('limiter', {
inserted(el, binding, vnode) {
el.addEventListener('input', event => {
const newValue = event.target.value;
const cleaned = newValue.replace(/[^A-Za-z]/g, '');
if (newValue !== cleaned) {
event.target.value = cleaned;
event.target.dispatchEvent(new Event('input'));
}
});
}
});
new Vue({
el: "#app",
data() {
return {
name: '',
};
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
del {
color: rgba(0, 0, 0, 0.3);
}
<script src="//unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
<input v-model="name" v-limiter />
<div>
Your name is {{ name }}.
</div>
</div>

Using Vue to enhance existing Multi page / server rendered / classic web app

I’v been searching for hours now and didn’t find anything close to my use case.
I have a classic / multi page / server rendered e-commerce webstie made with JAVA
I have a page where the server renders a list of products with a pagination
Today, i use jQuery to do the pagination to give a better loading experience to the user
On my server, if the request is AJAX a send a json response, else i render a normal html view
With jQuery and vanilla it’s really easy, with Vue it doesn’t seem to work because Vue’s v-for and other template binding replaces the server rendered template directly…
The server would render this :
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
article {
margin: 8px 0;
background: #eee;
padding: 20px;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
del {
color: rgba(0, 0, 0, 0.3);
}
<!-- server rendered -->
<div id="app">
<h2>Freelance list</h2>
<article>
<h3>louane</h3>
<p>
City : <strong>courbevoie</strong>
<br> Phone : <strong>05-36-23-51-89</strong>
</p>
</article>
<article>
<h3>indra</h3>
<p>
City : <strong>rheden</strong>
<br> Phone : <strong>(354)-415-2419</strong>
</p>
</article>
<article>
<h3>angelo</h3>
<p>
City : <strong>montpreveyres</strong>
<br> Phone : <strong>(883)-474-9314</strong>
</p>
</article>
prev
next
</div>
<!-- server rendered -->
I want to be able to do something like this but with Vue :
// fake url link, normally this would be taken from the href or something
var url = 'https://randomuser.me/api/?seed=abc&results=3&page=';
var page = 1;
var $articles = $('.articles');
var tpl = $articles.children().eq(0).clone();
$('.prev').click(function(e) {
e.preventDefault();
if (page <= 1) {
return
}
page--;
$.getJSON(url + page)
.done(onReqDone);
});
$('.next').click(function(e) {
e.preventDefault();
page++;
$.getJSON(url + page)
.done(onReqDone);
});
function onReqDone(res) {
$articles.html('');
res.results.forEach(function(user) {
var $node = tpl.clone();
$node.find('h3').text(user.name.first);
$node.find('strong:eq(0)').text(user.location.city);
$node.find('strong:eq(1)').text(user.phone);
$articles.append($node);
window.scroll(0, 0);
});
}
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
article {
margin: 8px 0;
background: #eee;
padding: 20px;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
del {
color: rgba(0, 0, 0, 0.3);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!-- server rendered -->
<div id="app">
<h2>Freelance list</h2>
<div class="articles">
<article>
<h3>louane</h3>
<p>
City : <strong>courbevoie</strong>
<br> Phone : <strong>05-36-23-51-89</strong>
</p>
</article>
<article>
<h3>indra</h3>
<p>
City : <strong>rheden</strong>
<br> Phone : <strong>(354)-415-2419</strong>
</p>
</article>
<article>
<h3>angelo</h3>
<p>
City : <strong>montpreveyres</strong>
<br> Phone : <strong>(883)-474-9314</strong>
</p>
</article>
</div>
prev
next
</div>
<!-- server rendered -->
Any ideo on how to do it ? Here are my tries :
https://jsfiddle.net/7270zft3/2/ : problem, it doesn’t remove the old dom
PS :
Before anyone talks about SSR with Vue ou just doing an SPA, here’s why i cant :
This e-commerce website can’t be totally re made with a Single Page App, it will cost too much time and money for the benefit it would bring to us
This e-commerce needs SEO to continue to drive traffic, just like any e-commerce btw
If Vue can really be used like jQuery (this is why we bet on Vue), we should be able to do this without doing a full rewrite
Event if we had time to rewrite an SPA, we can’t use SSR because our backend is made with JAVA and SSR seems to only be available with node and PHP with v8js module
You can attach a DOM ref to the server rendered content and then just as in jQuery, just clear the contents to the DOM element.
You need to perform this action only once, so you can may be add checks to see if your DOM ref is empty if page === 1
new Vue({
el: "#app",
data: {
users: null,
page: 1
},
methods: {
loadData: function(prev) {
var page = this.page
if (prev) {
page--
} else {
page++
}
fetch('https://randomuser.me/api/?seed=abc&results=3&page=' + page)
.then(res => res.json())
.then(data => {
this.$refs.serverContent.innerHTML = '';
this.users = data.results
this.page = page
window.scroll(0, 0)
})
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
article {
margin: 8px 0;
background: #eee;
padding: 20px;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
del {
color: rgba(0, 0, 0, 0.3);
}
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="app">
<h2>Freelance list</h2>
<div ref="serverContent">
<article>
<h3>louane</h3>
<p>
City : <strong>courbevoie</strong>
<br> Phone : <strong>05-36-23-51-89</strong>
</p>
</article>
<article>
<h3>indra</h3>
<p>
City : <strong>rheden</strong>
<br> Phone : <strong>(354)-415-2419</strong>
</p>
</article>
<article>
<h3>angelo</h3>
<p>
City : <strong>montpreveyres</strong>
<br> Phone : <strong>(883)-474-9314</strong>
</p>
</article>
</div>
<!-- Vue part -->
<!-- how to plug Vue to handle the pagination ? -->
<article v-for="user in users">
<h3>{{ user.name.first }}</h3>
<p>
City : <strong>{{ user.location.city }}</strong>
<br> Phone : <strong>{{ user.phone }}</strong>
</p>
</article>
<!-- Vue part -->
<button v-show="page > 1" #click="loadData(true)">prev</button>
<button #click="loadData()">next</button>
</div>
<!-- server rendered -->
But to do it Vue style, it is advised that you remove the rendering of first page from the server and let Vue handle data-fetching by itself so that Vue can use the data it stores as the source of truth instead of manipulating the DOM.
An example of hydration. I wasn't able to get Vue to stop warning me that my generated HTML didn't match the original; it's not critical. In development, Vue will "bail and do a full render", but in production it will leave the pre-rendered. You just want to be sure they match, so that when it does update, it's what you expect.
I left jQuery in for the getJSON. Other than that, it's jQuery free.
// fake url link, normally this would be taken from the href or something
var url = 'https://randomuser.me/api/?seed=abc&results=3&page=';
var page = 1;
$.getJSON(url + page).done((res) => {
const articles = res.results;
new Vue({
el: '#app',
template: `
<div id="app">
<h2>Freelance list</h2>
<div class="articles">
<article v-for="article in articles">
<h3>{{article.name.first}}</h3>
<p>
City : <strong>{{article.location.city}}</strong>
<br> Phone : <strong>{{article.phone}}</strong>
</p>
</article>
</div>
prev
next
</div>
`,
data: {
page,
url,
articles
},
methods: {
getPage() {
$.getJSON(this.url + this.page)
.done((res) => {
this.articles = res.results;
});
}
},
watch: {
page() {
this.getPage();
}
}
});
});
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
article {
margin: 8px 0;
background: #eee;
padding: 20px;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
del {
color: rgba(0, 0, 0, 0.3);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app" data-server-rendered="true">
<h2>Freelance list</h2>
<div class="articles">
<article>
<h3>louane</h3>
<p>
City : <strong>courbevoie</strong>
<br> Phone : <strong>05-36-23-51-89</strong>
</p>
</article>
<article>
<h3>indra</h3>
<p>
City : <strong>rheden</strong>
<br> Phone : <strong>(354)-415-2419</strong>
</p>
</article>
<article>
<h3>angelo</h3>
<p>
City : <strong>montpreveyres</strong>
<br> Phone : <strong>(883)-474-9314</strong>
</p>
</article>
</div>
prev
next
</div>
<!-- server rendered -->
Someone on the Vue forum found a good approach close to what was posted here but suited better my need : https://forum.vuejs.org/t/using-vue-to-enhance-existing-multi-page-server-rendered-classic-web-app/30934/20

How to update data on a page without refreshing on the vue.js?

My view is like this :
<div class="favorite" style="margin-bottom:5px;">
#if (Auth::user())
<add-favorite-store :id-store="{{ $store->id }}"></add-favorite-store>
#else
<a href="javascript:" class="btn btn-block btn-success">
<span class="fa fa-heart"></span> Favorite
</a>
#endif
</div>
My component is like this :
<template>
<a href="javascript:" class="btn btn-block btn-success" #click="addFavoriteStore($event)">
<span class="fa fa-heart"></span> <label id="favoriteId">{{ store_id == 'responseFound' ? 'Un-Favorite' : 'Favorite' }}</label>
</a>
</template>
<script>
export default{
props:['idStore'],
mounted(){
this.checkFavoriteStore()
},
methods:{
addFavoriteStore(event){
var label = $('#favoriteId')
var text = label.text()
event.target.disabled = true
const payload= {id_store: this.idStore}
if(text == "Favorite") {
this.$store.dispatch('addFavoriteStore', payload)
}
else {
this.$store.dispatch('deleteFavoriteStore', payload)
}
setTimeout(function () {
location.reload(true)
}, 1500)
},
checkFavoriteStore(){
const payload= {id_store: this.idStore}
var data = this.$store.dispatch('checkFavoriteStore', payload)
data.then((res) => this.store_id = res)
}
},
data() {
return {
store_id: ''
}
}
}
</script>
On the my code above, I using
location.reload(true)
to update data on the page. But it's reload the page.
I want when update the page, it's not reload the page
How can I do it?
Ok Here is a simple use case but less complicated as yours and using vuejs as it should be used. (http://codepen.io/simondavies/pen/MJOQEW)
OK let laravel/php code get the store ID as well as if its already been favorited. This way your script is not first checking the store to then decide what to do.
What this does is sends the store-id and the is-favorited through the component like:
<favorite :store-id="{{$store->id}}" :is-favorited="{{$store->isFavorited}}"></favorite>
Then the Vue component will update the button to display if its already liked (red) or not (grey), and then also handle the click event and update accordingly as well.
As you are using Laravel to tell the component if it's already favorited you can get rid of your checking function and one less http request. Then you only need to then update the store when the user clicks the favourite button.
And as seen in the demo it updates, no need to refresh.
I hope this help you re-write yours so you get waht you want.
PS i have left out the Logged IN check #if (Auth::user()) you have so you can put that back in etc
Vue.component('favorite', {
template: '<button type="button" #click.prevent="updateFaviteOption" :class="{ \'is-favorited\': isFavorited }"><span class="fa fa-heart"></span></button>',
props: [
'storeId',
'isFavorited'
],
data: function() {
return {}
},
methods: {
updateFaviteOption: function() {
this.isFavorited = !this.isFavorited;
///-- DO YOU AJAX HERE i use axios
///-- so you can updte the store the the this.isFavorited
}
}
});
new Vue({
el: '#app',
});
.favorite-wrapper {
margin: 20px auto;
padding: 0;
text-align: center;
width: 500px;
height: 100px;
}
a:link,
a:active,
a:visited {
text-decoration: none;
text-transform: uppercase;
color: #444;
transition: color 800ms;
font-size: 18px;
}
a:hover,
a:hover:visited {
color: purple;
}
button {
margin: 10px auto;
padding: 8px 10px;
background: transparent;
width: auto;
color: white;
text-transform: uppercase;
transition: color 800ms;
border-radius: 4px;
border: none;
outline: none;
}
button:hover {
cursor: pointer;
color: #058A29;
}
.fa {
color: #d9d9d9;
font-size: 20px;
transition: color 400ms, transform 400ms;
opacity: 1;
}
.is-favorited .fa {
opacity: 1;
color: red;
transform: scale(1.4);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css" rel="stylesheet" />
<div id="app">
<div class="favorite-wrapper">
<favorite :store-id="3" :is-favorited="true">
</favorite>
</div>
</div>
Another solution explained in vue-router doc. Simply use the watch system applied to the $route native attribute. You'll be able to detect new route even if they use the same component and fetch the data in consequence.
What you need is vm-forceUpdate.
Force the Vue instance to re-render. Note it does not affect all child components, only the instance itself and child components with inserted slot content.
I haven't tried using this myself, just came across it while working on a project.
vue forceUpdate or you could just call the function in the setTimeout method.
setTimeout(function () {
this.checkFavoriteStore();
}, 1500)
You are using a lot of javascript here, you can use vue to do most of the work there.

Categories

Resources