i'm using vue + vuetify + typescript to build a login/register form.
According to official documentation from vuetify https://vuetifyjs.com/en/components/forms#creating-rules is possible add custom rules.
My question is: is possible extract this rules in separate files as classes, functions, etc...that could be parametrized from template to reuse it?
You can have your rules written to an external file. Here's an example where the rules are in a js variable:
Vue.use(Vuetify)
// or : import externalRules from '#/rules/MyRules.js'
var externalRules = [
v => !!v || 'Name is required',
v => (v && v.length <= 10) || 'Name must be less than 10 characters'
]
new Vue({
el: "#app",
data: {
valid: true,
name: '',
nameRules: externalRules
},
methods: {
validate() {
if (this.$refs.form.validate()) {
this.snackbar = true
}
},
reset() {
this.$refs.form.reset()
},
resetValidation() {
this.$refs.form.resetValidation()
}
}
})
<!DOCTYPE html>
<html>
<head>
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify/dist/vuetify.min.css" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
</head>
<body>
<div id="app">
<v-app>
<v-form ref="form" v-model="valid" lazy-validation>
<v-text-field v-model="name" :counter="10" :rules="nameRules" label="Name" required></v-text-field>
<v-btn :disabled="!valid" color="success" #click="validate">
Validate
</v-btn>
<v-btn color="error" #click="reset">
Reset Form
</v-btn>
<v-btn color="warning" #click="resetValidation">
Reset Validation
</v-btn>
</v-form>
</v-app>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify/dist/vuetify.js"></script>
</body>
</html>
Related
It is my first "try" with Vue + vue-select.
I have imported Vue and vue-select like it is explained in the vue-select documentation.
And my first try is this simple HTML page :
<!DOCTYPE html>
<html lang="en">
<head>
<!-- include VueJS first -->
<script src="https://unpkg.com/vue#latest"></script>
<!-- use the latest vue-select release -->
<script src="https://unpkg.com/vue-select#latest"></script>
<link
rel="stylesheet"
href="https://unpkg.com/vue-select#latest/dist/vue-select.css"
/>
</head>
<body>
<div id="app">
<h1>Vue Select</h1>
<v-select :options="options"></v-select>
</div>
<script>
Vue.component("v-select", VueSelect.VueSelect);
new Vue({
el: "#app",
data: {
options: ["foo", "bar", "baz"],
},
});
</script>
</body>
</html>
When I try this page, I have these errors in the console :
What it is wrong in this first try? When I understand this first example, it will be easier for the rest.
I just checked your code and seems this issue is due to the vue version you are using. I just used version 2.* and it is working.
Demo :
Vue.component("v-select", VueSelect.VueSelect);
new Vue({
el: "#app",
data: {
selected: 'foo',
options: ["foo", "bar", "baz"]
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-select/3.10.3/vue-select.js"></script>
<link rel="stylesheet" href="https://unpkg.com/vue-select/dist/vue-select.css"/>
<div id="app">
<h1>Vue Select</h1>
<v-select :options="options" v-model="selected"></v-select>
</div>
Update : Here is the Vue 3 version of v-select
const { createApp } = Vue
const { createVuetify } = Vuetify
const vuetify = createVuetify()
const app = createApp({
template: '#app-template',
data: () => ({
items: ['Foo', 'Bar', 'Fizz', 'Buzz'],
}),
}).use(vuetify).mount('#app')
<script src="https://unpkg.com/vue#next/dist/vue.global.js"></script>
<script src="https://unpkg.com/#vuetify/nightly#3.0.0-next-20220604.0/dist/vuetify.js"></script>
<link rel="stylesheet" href="https://unpkg.com/#vuetify/nightly#3.0.0-next-20220604.0/dist/vuetify.css"/>
<link rel="stylesheet" href="https://unpkg.com/#mdi/font#6.x/css/materialdesignicons.min.css"/>
<script type="text/x-template" id="app-template">
<v-app>
<v-container fluid>
<v-select
:items="items"
></v-select>
</v-container>
</v-app>
</script>
<div id="app"></div>
I'm currently working on a project based on Vuetify. I need to insert a <a> tag and icon inside an internationalization text. Generally, it is easy to insert a variable as below shows,
this.$vuetify.lang.$t('I'm {0}', varirable_name)
but in this way, I cannot insert <a> tag or an icon <v-icon>, how could I achieve it?
Just like this,
`this.$vuetify.lang.$t('Here is an icon {0} and a {1}', <v-icon />, <a>link</a>)`
You can use v-html directive to output raw HTML. It will work for basic tags but won't work for Vuetify's v-tags (for example, v-icon).
new Vue({
vuetify: new Vuetify(),
data() {
return {
message: this.$vuetify.lang.t('Here is an bold {0} and a {1}', "<strong>text</strong>", "<a>link</a>")
}
}
}).$mount('#app')
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/#mdi/font#6.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.min.css" rel="stylesheet">
<div id="app">
<v-app>
<v-main>
<v-container><span v-html="message"></span ></v-container>
</v-main>
</v-app>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.js"></script>
I wouldn't recommend using Vuetify tags in composite format strings. Instead, translate the text inside of the tag.
<v-btn class="ma-2" color="primary" dark>
Accept // <- translate only this
<v-icon dark right>
mdi-checkbox-marked-circle
</v-icon>
</v-btn>
Due to security reasons we cannot install nodejs and any package managers. THerefore, I am trying to build my SPA with cdn support only. However, I am struggling to get it to work as I keep getting the failed to mount template error when running my code. I am using ASP.NET core 3.1 and i am able to get to the page to load up my partial views showing the side navigation and top navigation items. The page loads up and the router seems to work in changing the url in browser but the view components for the actual page templates do not show up on the screen. For instance dashboard view should show up but does not and therefore i believe this is where the issue is but I cannot see any issues with my code.
My code is as follows:
_vueapp:
<!DOCTYPE html>
<html lang="en">
<head>
#RenderSection("Styles", required: false)
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>#ViewData["Title"] - ARMS 2.0</title>
<link rel="stylesheet" href="~/css/sidebar.css" />
<link rel="stylesheet" href="https://pro.fontawesome.com/releases/v5.10.0/css/all.css" integrity="sha384-AYmEC3Yw5cVb3ZcuHtOA93w35dYTsvhLPVnYs9eStHfGJvOvKxVfELGroGkvsg+p" crossorigin="anonymous" />
</head>
<body>
#RenderBody()
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.12/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
#RenderSection("Scripts", required: false)
</body>
</html>
index file
#page
#model ARMS_2._0_LOCAL.Pages.vue.IndexModel
#{
Layout = "_Vueapp";
}
<div id="app" v-cloak>
<side-navigation></side-navigation>
<top-navigation></top-navigation>
<router-view></router-view>
</div>
#section Scripts
{
<partial name="components/side-navigation" />
<partial name="components/top-navigation" />
<partial name="views/dashboard" />
<partial name="views/reviews" />
<script>
//setup routing using SPA VUE interface
Vue.use(VueRouter);
const routes = [
{ path: '/', component: dashboard },
{ path: '/reviews', component: reviews }
]
const router = new VueRouter({
routes // short for `routes: routes`
})
var app = new Vue({
el: '#app',
router
}).$mount('#app')
</script>
}
side-navigation:
<style>
</style>
<template id="side-navigation">
<div>
<router-link to="/">Home</router-link>
<router-link to="/reviews">Reviews</router-link>
</div>
</template>
<script>
Vue.component('side-navigation', {
template: '#side-navigation'
})
</script>
one of my views which is dashboard:
<style>
</style>
<template id="dashboard">
<div>
<h1>dashboard</h1>
</div>
</template>
<script>
Vue.component('dashboard', {
template: '#dashboard'
})
</script>
You need to assign the components (dashboard and reviews) to a constant, otherwise the router can not recognize them.
dashboard:
<style>
</style>
<template id="dashboard">
<div>
<h1>dashboard</h1>
</div>
</template>
<script>
const dashboard = Vue.component('dashboard', {
template: '#dashboard'
})
</script>
reviews:
<style>
</style>
<template id="reviews">
<div>
<h1>reviews</h1>
</div>
</template>
<script>
const reviews = Vue.component('reviews', {
template: '#reviews'
})
</script>
I am working with BootstrapVue and am trying to dynamically create components after the site is rendered (I want to use asynchronous data for the generation process later) with Vue and add them to the DOM.
(I am simulating the asynchrony in the demo by creating the components with a 1 second delay after the page is loaded).
This is what happens:
Vue is creating the components and then they should be mounted to the DOM. But sadly they don't show up. Instead this comment: <!--function(e,n,r,i){return Pt(t,e,n,r,i,!0)}--> is added to them DOM (at the correct position though) in place of the actual HTML code of the component.
This is the jsfiddle with example code that demonstrates the problem: https://jsfiddle.net/0stdxorj/1
Thank you for your help :).
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<title>Test</title>
<!-- Bootstrap Core CSS -->
<link
type="text/css"
rel="stylesheet"
href="https://unpkg.com/bootstrap/dist/css/bootstrap.min.css"
/>
<!-- Bootstrap Vue CSS -->
<link
type="text/css"
rel="stylesheet"
href="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.css"
/>
<!-- Custom CSS -->
<link href="../vrc/vrc.css" rel="stylesheet">
<!-- Using: https://bootstrap-vue.org/ -->
<!-- Load polyfills to support older browsers -->
<script src="https://polyfill.io/v3/polyfill.min.js?features=es2015%2CIntersectionObserver"></script>
<!-- Vue JS -->
<script src="https://unpkg.com/vue#latest/dist/vue.min.js"></script>
<!-- Bootstrap Vue JS -->
<script src="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue-icons.min.js"></script>
<!-- Portal Vue JS -->
<script src="https://unpkg.com/portal-vue#latest/dist/portal-vue.umd.min.js"></script>
<!-- Popper JS -->
<script src="https://unpkg.com/#popperjs/core#2"></script>
<!-- VCalendar JS -->
<script src='https://unpkg.com/v-calendar'></script>
</head>
<body>
<div id="app">
<!-- Application root element -->
<b-container fluid="xl">
<b-row align-h="center" align-v="start" id="card-container">
<!-- Card Inline Template -->
<card inline-template id="card-template">
<b-card
v-bind:title=card.title
img-src="https://picsum.photos/600/300/?image=25"
img-alt="Image"
img-top
style="max-width: 370px; margin: 5px"
class="no-select"
>
<b-button v-b-toggle="'collapse-' + card.id">Button</b-button>
<b-collapse v-bind:id="'collapse-' + card.id" class="mt-2">
<v-calendar
is-expanded
:min-date='new Date()'
>
</v-calendar>
</b-collapse>
</b-card>
</card>
</b-row>
</b-container>
</div>
</body>
</html>
const vue = new Vue({
el: '#app'
})
const cardComponentConstructor = Vue.extend({
props: {
card: {
required: true,
default: {id: 0, title: 'Default.'}
}
},
template: '#card-template',
mounted() {
alert("mounted " + this.card.id + " " + this.card.title)
},
created() {
//alert("created " + this.card.id + " " + this.card.title)
}
});
window.addEventListener("load", function(event) {
setTimeout(function () {
createCard({propsData: {card: {id: 0, title: '0'}}})
createCard({propsData: {card: {id: 1, title: '1'}}})
}, 1000);
});
function createCard(props) {
let componentInstance = new cardComponentConstructor(props)
componentInstance.$mount()
document.getElementById("card-container").appendChild(componentInstance.$el)
}
I fixed the problem by going at it in another way. Vue re-renders v-for tags when using a key attribute (:key="card.id"). So I don't try to manually create the components anymore but make use of this feature and modify the cards data instead, which in turn then makes Vue re-render the deleted/modified/added cards:
Updated jsfiddle with my solution https://jsfiddle.net/t7k49a2n/
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<title>Test</title>
<!-- Bootstrap Core CSS -->
<link
type="text/css"
rel="stylesheet"
href="https://unpkg.com/bootstrap/dist/css/bootstrap.min.css"
/>
<!-- Bootstrap Vue CSS -->
<link
type="text/css"
rel="stylesheet"
href="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.css"
/>
<!-- Custom CSS -->
<link href="../vrc/vrc.css" rel="stylesheet">
<!-- Using: https://bootstrap-vue.org/ -->
<!-- Load polyfills to support older browsers -->
<script src="https://polyfill.io/v3/polyfill.min.js?features=es2015%2CIntersectionObserver"></script>
<!-- Vue JS -->
<script src="https://unpkg.com/vue#latest/dist/vue.min.js"></script>
<!-- Bootstrap Vue JS -->
<script src="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue-icons.min.js"></script>
<!-- Portal Vue JS -->
<script src="https://unpkg.com/portal-vue#latest/dist/portal-vue.umd.min.js"></script>
<!-- Popper JS -->
<script src="https://unpkg.com/#popperjs/core#2"></script>
<!-- VCalendar JS -->
<script src='https://unpkg.com/v-calendar'></script>
</head>
<body>
<div id="app">
<!-- Application root element -->
<b-container fluid="xl">
<b-row align-h="center" align-v="start">
<!-- Card Inline Template -->
<card
inline-template
v-for="card in cards"
v-bind="{card: card}"
:key="card.id"
>
<b-card
v-bind:title=card.title
img-src="https://picsum.photos/600/300/?image=25"
img-alt="Image"
img-top
style="max-width: 370px; margin: 5px"
class="no-select"
>
<b-button v-b-toggle="'collapse-' + card.id">Button</b-button>
<b-collapse v-bind:id="'collapse-' + card.id" class="mt-2">
<v-calendar
is-expanded
:min-date='new Date()'
>
</v-calendar>
</b-collapse>
</b-card>
</card>
</b-row>
</b-container>
</div>
</body>
</html>
Vue.component('card', {
props: {
card: {
required: true,
default: {id: 0, title: 'Default.'}
}
}
});
window.app = new Vue({
el: '#app',
data: {
cards: []
}
})
window.addEventListener("load", function(event) {
setTimeout(function () {
window.app.cards.push({id: 0, title: '1'})
}, 1000);
});
I have an issue with loading Leaflet map using Vue.js and Bulma tab components (via Buefy).
If map is placed inside tab then it does not load all tiles until browser window is resized.
If map is placed outside of Bulma tabs component then it loads without any issue.
Calling map.invalidateSize() seems to help, but to do it automatically when tab changes I have to call it using setTimeout and put very big delay, like 1sec - which is very ugly.
How to get this working without this invalidateSize workaround?
Example with the issue: https://codepen.io/alxxnder/pen/zyYxwd
Example without the issue: https://codepen.io/alxxnder/pen/LMYEjr
Code:
new Vue({
el: '#app',
data: {
map: null,
},
methods: {
invalidateSize: function() {
this.map.invalidateSize();
}
},
mounted() {
this.map = L.map('map').setView([38.63, -90.23], 12);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(this.map);
}
})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Leaflet Test</title>
<link rel="stylesheet" href="https://unpkg.com/buefy#0.7/dist/buefy.min.css">
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.3.4/dist/leaflet.css">
</head>
<body>
<div class="section">
<div class="container" id="app">
<b-tabs position="is-centered">
<b-tab-item label="Tab 1">
<div class="section">
Tab 1
<div class="map" id="map" style="height: 400px; width: 100%"></div>
<button class="button is-info" #click="invalidateSize()">invalidateSize</button>
</div>
</b-tab-item>
<b-tab-item label="Tab 2">
<div class="section">
Tab 2
</div>
</b-tab-item>
</b-tabs>
</div>
</div>
</body>
<script src="https://unpkg.com/vue#2"></script>
<script src="https://unpkg.com/buefy#0.7/dist/buefy.min.js"></script>
<script src="https://unpkg.com/leaflet#1.3.4/dist/leaflet.js"></script>
</html>
As explained in Data-toggle tab does not download Leaflet map, the issue is caused by the fact that your map container does not have yet its full size when you initialize it. You may understand it more easily if your map had been in an initially hidden tab (e.g. in tab 2).
As for your initially active tab (i.e. tab 1), it is probable that Buefy / Bulma still takes some time to reveal the tab content.
Since there is no event when the tab transition completes, you have to wait for the transition duration before calling the invalidateSize method. In your case, 300ms seems to be fine.
Then you should also call it again when the user changes the tab (see Buefy tabs events), otherwise should the browser had changed size while your tab was hidden, the same issue would happen again.
Demo with maps in the 2 tabs:
new Vue({
el: '#app',
data: {
map: null,
map2: null,
tabMaps: []
},
methods: {
invalidateSize: function(tabIndex) {
setTimeout(() => {
if (typeof tabIndex === "number") {
this.tabMaps[tabIndex].invalidateSize();
} else {
// invalidate all maps
this.tabMaps.forEach(map => {
map.invalidateSize();
});
}
}, 300);
}
},
mounted() {
this.map = L.map('map').setView([38.63, -90.23], 12);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(this.map);
// map2 in tab2
this.map2 = L.map(this.$refs.map2).setView([38.63, -90.23], 12);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png").addTo(
this.map2
);
this.tabMaps.push(this.map); // 0
this.tabMaps.push(this.map2); // 1
this.invalidateSize();
}
})
<link rel="stylesheet" href="https://unpkg.com/buefy#0.7/dist/buefy.min.css">
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.3.4/dist/leaflet.css">
<div class="section">
<div class="container" id="app">
<b-tabs #change="invalidateSize" position="is-centered">
<b-tab-item label="Tab 1">
<div class="section">
Tab 1
<div class="map" id="map" style="height: 400px; width: 100%"></div>
<button class="button is-info" #click="invalidateSize()">invalidateSize</button>
</div>
</b-tab-item>
<b-tab-item label="Tab 2">
<div class="section">
Tab 2
<div class="map" ref="map2" style="height: 400px; width: 100%"></div>
</div>
</b-tab-item>
</b-tabs>
</div>
</div>
<script src="https://unpkg.com/vue#2"></script>
<script src="https://unpkg.com/buefy#0.7/dist/buefy.min.js"></script>
<script src="https://unpkg.com/leaflet#1.3.4/dist/leaflet.js"></script>