Password Strength meter for Vue.js 3 - vue.js

I've started working with Vue.js version 3 and making a simple signup form. I need to implement a password strength meter for my password field but seems there isn't any compatible such component with Vue.js 3 version.
I've found few good components for password strength meter to use with Vue.js but they all seems to have compatibility with Vue.js 2.
I've tried
https://awesomeopensource.com/project/skegel13/vue-password
its working good in DEMO but not compatible with my Vue.js 3.
I'm stuck here. Any help/suggestions ?

Are you looking for a visual component or something that actually computes password strength?
zxcvbn is fairly well-known as a strength calculator - it outputs a score from 0-4 for how strong a password is. You could then roll a simple Vue component that outputs a different value depending on that score.
Below example uses Tailwind CSS classes for styling the visual meter. I wrote this in the browser and haven't tested the Vue but it's fairly simple and you should be able to get the idea.
<!-- PasswordStrengthMeter.vue -->
<template>
<div>
<div class="w-full h-4 flex">
<div :class="style"></div>
<div class="flex-1"></div>
</div>
<div>{{ strength }}</div>
</div>
</template>
<script>
props: {
score: {
required: true,
default: 0,
}
},
computed: {
strength() {
return [
'Very Weak', // 0
'Weak', // 1
'Moderate', // 2
'Strong', // 3
'Very Strong' // 4
][this.score];
},
style() {
return [
'w-1 bg-red-500', // 0
'w-1/4 bg-yellow-500', // 1
'w-1/2 bg-yellow-300', // 2
'w-3/4 bg-green-500', // 3
'w-full bg-blue-500' // 4
][this.score];
},
},
</script>
Here's what it might look like.

This one works nicely with Vue3.
https://github.com/miladd3/vue-simple-password-meter/tree/next
Sample code from the repository:
<template>
<div id="app">
<label>Password</label>
<input type="password" v-model="password" />
<password-meter :password="password" />
</div>
</template>
<script>
import { defineComponent, ref } from 'vue';
import PasswordMeter from 'vue-simple-password-meter';
export default defineComponent({
components: {
PasswordMeter,
},
setup() {
const password = ref('');
return {
password,
};
},
});
</script>

Related

Tailwind color not working on dynamic classes Vue + Vite

First time Im using tailwind and I can't figure out why colors are not working. This is a fresh install of Laravel Jetstream which comes with Tailwind, Vue3, Vite & Inertia.
seems like the relevant styling is not imported from tailwind if classes are added dynamically.
Here's some basic component
<template>
<div :class="style" class="border-l-4 p-4" role="alert">
<p><slot name="headline"></slot></p>
<p class="pt-3" v-if="slots.error"><span class="font-bold">Message:</span><slot name="error"></slot></p>
<div class="my-3" v-if="slots.info"><slot name="info"></slot></div>
</div>
</template>
<script setup>
import { useSlots } from 'vue'
const slots = useSlots()
</script>
<script>
export default {
name: 'Alert',
props: {
color: {type: String, default: 'red'}
},
computed: {
style() {
return `bg-${this.color}-100 border-${this.color}-500 text-${this.color}-700`
}
}
}
</script>
and using something like this does not have any color related styling although the class is there
<Alert color="orange" class="my-5">
<template #headline>Test</template>
</Alert>
but if the dynamic classes are also specified somewhere in the same page then everything works.
i.e.
<div class="bg-orange-100 border-orange-500 text-orange-700"></div>
<Alert color="orange" class="my-5">
<template #headline>Test</template>
</Alert>
This was relatively an easy fixed, it was mentioned in here to avoid constructing class name dynamically
https://tailwindcss.com/docs/content-configuration#dynamic-class-names
so, in the computed style I just specify full class name conditionally with all the possible values
changed from this.
style() {
return `bg-${this.color}-100 border-${this.color}-500 text-${this.color}-700`
}
to this
style() {
const styles = {
default : 'bg-cyan-100 border-cyan-500 text-cyan-700',
red : 'bg-red-100 border-red-500 text-red-700',
orange: 'bg-orange-100 border-orange-500 text-orange-700',
green: 'bg-green-100 border-green-500 text-green-700',
blue: 'bg-blue-100 border-blue-500 text-blue-700',
}
return styles[this.color] ?? styles.default
}
now everything works perfectly
The method I always use is to simply put in all the possible classes inside the file when dealing with some basic dynamic classes. I noticed that even if the classes are specified in commented lines, tailwind still import the styling of those classes when found inside any file
here's an example
<template>
<div :class="`bg-${color}-100 border-${color}-500 text-${color}-700`" class="border-l-4 p-4" role="alert">
test
</div>
</template>
<script>
/* all supported classes for color props
bg-red-100 border-red-500 text-red-700
bg-orange-100 border-orange-500 text-orange-700
bg-green-100 border-green-500 text-green-700
bg-blue-100 border-blue-500 text-blue-700
*/
export default {
name: 'Alert',
props: {
color: {type: String, default: 'red'}
}
}
</script>
So now all these would work fine
<Alert color="red"></Alert>
<Alert color="orange"></Alert>
<Alert color="green"></Alert>
<Alert color="blue"></Alert>
but this one wont have any styling as the generated classes for purple are not pre specified in any files
<Alert color="purple"></Alert>

vue: Populate select options dynamically

I have vuejs based application with with a form with a drop-down menu. The options in the menu should eventually be populated from a rest call. Right now I use a very hard coded list:
<template>
<ModalForm v-show="visible" #close="visible = false" title="Add location">
<div class="form-outline mb-4">
<select
class="form-control form-control-lg"
placeholder="Organisation"
v-model="formData.organisation"
>
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select>
</div>
<div class="text-center mt-5">
<button class="btn btn-lg btn-primary">
Add location
</button>
</div>
</ModalForm>
</template>
<script setup>
import { reactive, ref } from "#vue/reactivity";
import { watch } from "#vue/runtime-core";
import ModalForm from "../../../components/ModalForm.vue";
const visible = ref(false);
const formDataDefaults = {
organisation : "",
};
const formData = reactive({ ...formDataDefaults });
watch(visible, async (newValue) => {
if (newValue == false) {
for (var key in formData) formData[key] = formDataDefaults[key];
}
});
function show() {
visible.value = true;
}
defineExpose({ show });
</script>
and that kind of works - but when I try to populate the select options in a slightly more dynamic way things fail in the build stage. From googling around I have tried this:
<option v-for="org in organisations" :value="org.name" :key="org.id">
...
<script setup>
export default {
data() {
return { organisation: [{name: "Org1", id: 1}, {name: "Org2", id: 2}] };
}
};
</script>
When compiling/running this with npm run serve I get the following error message:
error in ./src/pages/Locations/components/AddLocationModal.vue?vue&type=script&setup=true&lang=js
Syntax Error: TypeError: Cannot read properties of null (reading 'content')
which I do not understand, but I think it is related to the data export in the <script> section. Of course the next step is to actually populate with a rest call - but one step at a time.
Any hints appreciated!
Disclaimer: I am quite new to vue (and anything beyond basic JS), so this is in it's entirety based on CopyPaste and Google.
Update: the error message comes when I only add the
<script setup>
export default {
data() {
return { organisation: [{name: "Org1", id: 1}, {name: "Org2", id: 2}] };
}
};
</script>
part - without making any changes to the <template> part. So it seems there is "something" wrong with the data export. I guess problem is I struggle to understand the model for flow of data/state in this framework.
Update 2: As pointed out in answer below there was a missing { in the original data export section. Fixing that did not solve the problem.
Update 3/solution: As pointed out by several I had a mix of vue2 and vue3 syntax. This example solved it for me.
<script setup> is only for use in Vue 3 projects using the Composition API. Your current code within <script setup> however is written using the Options API. You can either:
Refactor your code to use the Composition API using script setup,
Remove the keyword setup from <script setup> to stick with your current code which should then work using the Options API. Vue 2 projects must use the Options API.
In the example code you have a syntax error, you're missing a brace:
data() {
˅
return { organisation: [{name: "Org1", id: 1}, {name: "Org2", id: 2}] };
˄
}
Check the official documentation about SELECT or the dynamic example

HowTo: Toggle dark mode with TailwindCSS + Vue3 + Vite

I'm a beginner regarding Vite/Vue3 and currently I am facing an issue where I need the combined knowledge of the community.
I've created a Vite/Vue3 app and installed TailwindCSS to it:
npm create vite#latest my-vite-vue-app -- --template vue
cd my-vite-vue-app
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
Then I followed the instructions on Tailwind's homepage:
Add the paths to all of your template files in your tailwind.config.js file.
Import the newly-created ./src/index.css file in your ./src/main.js file. Create a ./src/index.css file and add the #tailwind directives for each of Tailwind’s layers.
Now I have a working Vite/Vue3/TailwindCSS app and want to add the feature to toggle dark mode to it.
The Tailwind documentation says this can be archived by adding darkMode: 'class' to tailwind.config.js and then toggle the class dark for the <html> tag.
I made this work by using this code:
Inside index.html
<html lang="en" id="html-root">
(...)
<body class="antialiased text-slate-500 dark:text-slate-400 bg-white dark:bg-slate-900">
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
Inside About.vue
<template>
<div>
<h1>This is an about page</h1>
<button #click="toggleDarkMode">Toggle</botton>
</div>
</template>
<script>
export default {
methods: {
toggleDarkMode() {
const element = document.getElementById('html-root')
if (element.classList.contains('dark')) {
element.classList.remove('dark')
} else {
element.classList.add('dark')
}
},
},
};
</script>
Yes, I know that this isn't Vue3-style code. And, yes, I know that one could do element.classList.toggle() instead of .remove() and .add(). But maybe some other beginners like me will look at this in the future and will be grateful for some low-sophisticated code to start with. So please have mercy...
Now I'll finally come to the question I want to ask the community:
I know that manipulating the DOM like this is not the Vue-way of doing things. And, of course, I want to archive my goal the correct way. But how do I do this?
Believe me I googled quite a few hours but I didn't find a solution that's working without installing this and this and this additional npm module.
But I want to have a minimalist approach. As few dependancies as possbile in order not to overwhelm me and others that want to start learning.
Having that as a background - do you guys and gals have a solution for me and other newbies? :-)
The target element of your event is outside of your application. This means there is no other way to interact with it other than by querying it via the DOM available methods.
In other words, you're doing it right.
If the element was within the application, than you'd simply link class to your property and let Vue handle the specifics of DOM manipulation:
:class="{ dark: darkMode }"
But it's not.
As a side note, it is really important your toggle method doesn't rely on whether the <body> element has the class or not, in order to decide if it should be applied/removed. You should keep the value saved in your app's state and that should be your only source of truth.
That's the Vue principle you don't want break: let data drive the DOM state, not the other way around.
It's ok to get the value (on mount) from current state of <body>, but from that point on, changes to your app's state will determine whether or not the class is present on the element.
vue2 example:
Vue.config.devtools = false;
Vue.config.productionTip = false;
new Vue({
el: '#app',
data: () => ({
darkMode: document.body.classList.contains('dark')
}),
methods: {
applyDarkMode() {
document.body.classList[
this.darkMode ? 'add' : 'remove'
]('dark')
}
},
watch: {
darkMode: 'applyDarkMode'
}
})
body.dark {
background-color: #191919;
color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.14/vue.js"></script>
<div id="app">
<label>
<input type="checkbox" v-model="darkMode">
dark mode
</label>
</div>
vue3 example:
const {
createApp,
ref,
watchEffect
} = Vue;
createApp({
setup() {
const darkMode = ref(document.body.classList.contains('dark'));
const applyDarkMode = () => document.body.classList[
darkMode.value ? 'add' : 'remove'
]('dark');
watchEffect(applyDarkMode);
return { darkMode };
}
}).mount('#app')
body.dark {
background-color: #191919;
color: white;
}
<script src="https://unpkg.com/vue#next/dist/vue.global.prod.js"></script>
<div id="app">
<label>
<input type="checkbox" v-model="darkMode">
dark mode
</label>
</div>
Obviously, you might want to keep the state of darkMode in some external store, not locally, in data (and provide it in your component via computed), if you use it in more than one component.
What you're looking for is Binding Classes, but where you're getting stuck is trying to manipulate the <body> which is outside of the <div> your main Vue instance is mounted in.
Now your problem is your button is probably in a different file to your root <div id="app"> which starts in your App.vue from boilerplate code. Your two solutions are looking into state management (better for scalability), or doing some simple variable passing between parents and children. I'll show the latter:
Start with your switch component:
// DarkButton.vue
<template>
<div>
<h1>This is an about page</h1>
<button #click="toggleDarkMode">Toggle</button>
</div>
</template>
<script>
export default {
methods: {
toggleDarkMode() {
this.$emit('dark-switch');
},
},
};
</script>
This uses component events ($emit)
Then your parent/root App.vue will listen to that toggle event and update its class in a Vue way:
<template>
<div id="app" :class="{ dark: darkmode }">
<p>Darkmode: {{ darkmode }}</p>
<DarkButton #dark-switch="onDarkSwitch" />
</div>
</template>
<script>
import DarkButton from './components/DarkButton.vue';
export default {
name: 'App',
components: {
DarkButton,
},
data: () => ({
darkmode: false,
}),
methods: {
onDarkSwitch() {
this.darkmode = !this.darkmode;
},
},
};
</script>
While tailwind say for Vanilla JS to add it into your <body>, you generally shouldn't manipulate that from that point on. Instead, don't manipulate your <body>, only go as high as your <div id="app"> with things you want to be within reach of Vue.

vue2-autocomplete-js does not work as mentioned in documentation

I have Vue version 2.9.3.
I am trying to get names from a REST API URL:
http://192.168.1.3/api/clinic/patients?q=s
will return name of patients starting with S.
It by default takes q as parameter see docs
And following the docs I implemented it like shown below:
<template>
<div>
<div class="alignLabel">
<label for="patientName">
<span>Patient Name <span class="required">*</span></span>
</label>
</div>
<div>
<autocomplete
url="http://192.168.1.3/api/clinic/patients"
anchor="title"
label="writer"
:on-select="getData">
</autocomplete>
</div>
</template>
and in script:
var $ = window.jQuery = require('jquery')
import Vue from 'vue'
import Autocomplete from 'vue2-autocomplete-js';
Vue.use(Autocomplete)
require('vue2-autocomplete-js/dist/style/vue2-autocomplete.css');
export default {
name: 'Attend',
data : function (){ },
components: {
'vue-datetimepicker': vuedatetimepicker,
'autocomplete': Autocomplete,
},
methods: {
getData(obj){
console.log(obj); // this method is never getting called.
},
the getData is never getting called. Autocomplete is not working at all.

How to control the flow of vue application

I'm trying to make a game using vue and I designed the following structure:
<template>
<div v-if="status==='beforegamestart'" key="beforegamestart">
<button v-on:click="startGame()">Start</button>
</div>
<div v-else-if="status==='inprocess'" key="inprocess">
<h1>Game in process</h1>
<button v-on:click="finishGame()">Finish</button>
</div>
<div v-else-if="status==='gameover'" key="gameover">
<h2>Game over</h2>
</div>
<div v-else>
<h1>Else</h1>
</div>
</template>
<script>
export default {
name: 'GameComponent',
data() {
return {
status: 'beforegamestart'
}
},
methods: {
startGame: function() {
this.status = "inprocess";
},
finishGame: function() {
this.status = "gameover";
}
}
}
</script>
<style>
</style>
I wonder what is the common way to make a game like flappy bird in vue, which has a start menu, game in process, and a finish view? Is it OK to do it in this way? I just wonder how to change different views properly.
If I was was you, I'd do the following:
Step 1: Split the start, in-progress, and game-over screens into their own components. and import them into App.vue(or whatever you want) like so:
import Start from './Start.vue'
import Game from './Game.vue'
import GameOver from './GameOver.vue'
Step 2: Use a dynamic component in the template. See https://v2.vuejs.org/v2/guide/components.html#Dynamic-Components
<component :is="currentState"/>
Step 3: Have a method/computed property(or for bonus points, use vuex) that handles the current state of the game and have to components render based on that value.
computed: {
currentState: function() {
switch(status){
case "beforeGameStart":
return "Start";
break;
...
}
}
}