mirror of
https://github.com/Bubka/2FAuth.git
synced 2024-12-25 16:39:13 +01:00
Set up Form component & some form elements components
This commit is contained in:
parent
c02af5aab9
commit
1d51cb3e31
18
resources/js_vue3/app.js
vendored
18
resources/js_vue3/app.js
vendored
@ -42,13 +42,23 @@ app.use(Notifications)
|
|||||||
import ResponsiveWidthWrapper from '@/layouts/ResponsiveWidthWrapper.vue'
|
import ResponsiveWidthWrapper from '@/layouts/ResponsiveWidthWrapper.vue'
|
||||||
import FormWrapper from '@/layouts/FormWrapper.vue'
|
import FormWrapper from '@/layouts/FormWrapper.vue'
|
||||||
import Footer from '@/layouts/Footer.vue'
|
import Footer from '@/layouts/Footer.vue'
|
||||||
|
import VueButton from '@/components/formElements/Button.vue'
|
||||||
|
import FieldError from '@/components/formElements/FieldError.vue'
|
||||||
|
import FormField from '@/components/formElements/FormField.vue'
|
||||||
|
import FormPasswordField from '@/components/formElements/FormPasswordField.vue'
|
||||||
|
import FormButtons from '@/components/formElements/FormButtons.vue'
|
||||||
|
|
||||||
// Components registration
|
// Components registration
|
||||||
app
|
app
|
||||||
.component('font-awesome-icon', FontAwesomeIcon)
|
.component('FontAwesomeIcon', FontAwesomeIcon)
|
||||||
.component('responsive-width-wrapper', ResponsiveWidthWrapper)
|
.component('ResponsiveWidthWrapper', ResponsiveWidthWrapper)
|
||||||
.component('form-wrapper', FormWrapper)
|
.component('FormWrapper', FormWrapper)
|
||||||
.component('vue-footer', Footer)
|
.component('VueFooter', Footer)
|
||||||
|
.component('VueButton', VueButton)
|
||||||
|
.component('FieldError', FieldError)
|
||||||
|
.component('FormField', FormField)
|
||||||
|
.component('FormPasswordField', FormPasswordField)
|
||||||
|
.component('FormButtons', FormButtons)
|
||||||
|
|
||||||
// App mounting
|
// App mounting
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
34
resources/js_vue3/components/formElements/Button.vue
Normal file
34
resources/js_vue3/components/formElements/Button.vue
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: 'is-link'
|
||||||
|
},
|
||||||
|
nativeType: {
|
||||||
|
type: String,
|
||||||
|
default: 'submit'
|
||||||
|
},
|
||||||
|
isLoading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
isDisabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<button
|
||||||
|
:type="nativeType"
|
||||||
|
:disabled="isLoading || isDisabled"
|
||||||
|
:class="{
|
||||||
|
'button': true,
|
||||||
|
[`${color}`]: true,
|
||||||
|
'is-loading': isLoading,
|
||||||
|
}"
|
||||||
|
v-on:click="$emit('click')">
|
||||||
|
<slot />
|
||||||
|
</button>
|
||||||
|
</template>
|
18
resources/js_vue3/components/formElements/FieldError.vue
Normal file
18
resources/js_vue3/components/formElements/FieldError.vue
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
form: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
field: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div role="alert">
|
||||||
|
<p :id="'valError' + field[0].toUpperCase() + field.toLowerCase().slice(1)" class="help is-danger" v-if="form.errors.has(field)" v-html="form.errors.get(field)" />
|
||||||
|
</div>
|
||||||
|
</template>
|
318
resources/js_vue3/components/formElements/Form.js
vendored
Normal file
318
resources/js_vue3/components/formElements/Form.js
vendored
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
import { httpClientFactory } from '@/services/httpClientFactory'
|
||||||
|
import Errors from './FormErrors'
|
||||||
|
|
||||||
|
class Form {
|
||||||
|
/**
|
||||||
|
* Create a new form instance.
|
||||||
|
*
|
||||||
|
* @param {Object} data
|
||||||
|
*/
|
||||||
|
constructor (data = {}) {
|
||||||
|
this.axios = httpClientFactory('web')
|
||||||
|
this.isBusy = false
|
||||||
|
this.isDisabled = false
|
||||||
|
// this.successful = false
|
||||||
|
this.errors = new Errors()
|
||||||
|
this.originalData = this.deepCopy(data)
|
||||||
|
|
||||||
|
Object.assign(this, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill form data.
|
||||||
|
*
|
||||||
|
* @param {Object} data
|
||||||
|
*/
|
||||||
|
fill (data) {
|
||||||
|
this.keys().forEach(key => {
|
||||||
|
this[key] = data[key]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update original form data.
|
||||||
|
*/
|
||||||
|
setOriginal () {
|
||||||
|
Object.keys(this)
|
||||||
|
.filter(key => !Form.ignore.includes(key))
|
||||||
|
.forEach(key => {
|
||||||
|
this.originalData[key] = this.deepCopy(this[key])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill form data.
|
||||||
|
*
|
||||||
|
* @param {Object} data
|
||||||
|
*/
|
||||||
|
fillWithKeyValueObject (data) {
|
||||||
|
this.keys().forEach(key => {
|
||||||
|
const keyValueObject = data.find(s => s.key === key.toString())
|
||||||
|
if(keyValueObject != undefined) {
|
||||||
|
this[key] = keyValueObject.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the form data.
|
||||||
|
*
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
data () {
|
||||||
|
return this.keys().reduce((data, key) => (
|
||||||
|
{ ...data, [key]: this[key] }
|
||||||
|
), {})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the form data keys.
|
||||||
|
*
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
keys () {
|
||||||
|
return Object.keys(this)
|
||||||
|
.filter(key => !Form.ignore.includes(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start processing the form.
|
||||||
|
*/
|
||||||
|
startProcessing () {
|
||||||
|
this.errors.clear()
|
||||||
|
this.isBusy = true
|
||||||
|
// this.successful = false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finish processing the form.
|
||||||
|
*/
|
||||||
|
finishProcessing () {
|
||||||
|
this.isBusy = false
|
||||||
|
// this.successful = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the form errors.
|
||||||
|
*/
|
||||||
|
clear () {
|
||||||
|
this.errors.clear()
|
||||||
|
// this.successful = false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the form fields.
|
||||||
|
*/
|
||||||
|
reset () {
|
||||||
|
Object.keys(this)
|
||||||
|
.filter(key => !Form.ignore.includes(key))
|
||||||
|
.forEach(key => {
|
||||||
|
this[key] = this.deepCopy(this.originalData[key])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit the form via a GET request.
|
||||||
|
*
|
||||||
|
* @param {String} url
|
||||||
|
* @param {Object} config (axios config)
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
get (url, config = {}) {
|
||||||
|
return this.submit('get', url, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit the form via a POST request.
|
||||||
|
*
|
||||||
|
* @param {String} url
|
||||||
|
* @param {Object} config (axios config)
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
post (url, config = {}) {
|
||||||
|
return this.submit('post', url, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit the form via a PATCH request.
|
||||||
|
*
|
||||||
|
* @param {String} url
|
||||||
|
* @param {Object} config (axios config)
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
patch (url, config = {}) {
|
||||||
|
return this.submit('patch', url, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit the form via a PUT request.
|
||||||
|
*
|
||||||
|
* @param {String} url
|
||||||
|
* @param {Object} config (axios config)
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
put (url, config = {}) {
|
||||||
|
return this.submit('put', url, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit the form via a DELETE request.
|
||||||
|
*
|
||||||
|
* @param {String} url
|
||||||
|
* @param {Object} config (axios config)
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
delete (url, config = {}) {
|
||||||
|
return this.submit('delete', url, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit the form data via an HTTP request.
|
||||||
|
*
|
||||||
|
* @param {String} method (get, post, patch, put)
|
||||||
|
* @param {String} url
|
||||||
|
* @param {Object} config (axios config)
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
submit (method, url, config = {}) {
|
||||||
|
this.startProcessing()
|
||||||
|
|
||||||
|
const data = method === 'get'
|
||||||
|
? { params: this.data() }
|
||||||
|
: this.data()
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// (Form.axios || axios).request({ url: this.route(url), method, data, ...config })
|
||||||
|
this.axios.request({ url: this.route(url), method, data, ...config })
|
||||||
|
.then(response => {
|
||||||
|
this.finishProcessing()
|
||||||
|
|
||||||
|
resolve(response)
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.isBusy = false
|
||||||
|
|
||||||
|
if (error.response) {
|
||||||
|
this.errors.set(this.extractErrors(error.response))
|
||||||
|
}
|
||||||
|
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit the form data via an HTTP request.
|
||||||
|
*
|
||||||
|
* @param {String} method (get, post, patch, put)
|
||||||
|
* @param {String} url
|
||||||
|
* @param {Object} config (axios config)
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
upload (url, formData, config = {}) {
|
||||||
|
this.startProcessing()
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// (Form.axios || axios).request({ url: this.route(url), method, data, ...config })
|
||||||
|
this.axios.request({ url: this.route(url), method: 'post', data: formData, header: {'Content-Type' : 'multipart/form-data'}, ...config })
|
||||||
|
.then(response => {
|
||||||
|
this.finishProcessing()
|
||||||
|
|
||||||
|
resolve(response)
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.isBusy = false
|
||||||
|
|
||||||
|
if (error.response) {
|
||||||
|
this.errors.set(this.extractErrors(error.response))
|
||||||
|
}
|
||||||
|
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the errors from the response object.
|
||||||
|
*
|
||||||
|
* @param {Object} response
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
extractErrors (response) {
|
||||||
|
if (!response.data || typeof response.data !== 'object') {
|
||||||
|
return { error: Form.errorMessage }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.data.errors) {
|
||||||
|
return { ...response.data.errors }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.data.message) {
|
||||||
|
return { error: response.data.message }
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...response.data }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a named route.
|
||||||
|
*
|
||||||
|
* @param {String} name
|
||||||
|
* @return {Object} parameters
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
|
route (name, parameters = {}) {
|
||||||
|
let url = name
|
||||||
|
|
||||||
|
if (Form.routes.hasOwnProperty(name)) {
|
||||||
|
url = decodeURI(Form.routes[name])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof parameters !== 'object') {
|
||||||
|
parameters = { id: parameters }
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(parameters).forEach(key => {
|
||||||
|
url = url.replace(`{${key}}`, parameters[key])
|
||||||
|
})
|
||||||
|
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear errors on keydown.
|
||||||
|
*
|
||||||
|
* @param {KeyboardEvent} event
|
||||||
|
*/
|
||||||
|
onKeydown (event) {
|
||||||
|
if (event.target.name) {
|
||||||
|
this.errors.clear(event.target.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deep copy the given object.
|
||||||
|
*
|
||||||
|
* @param {Object} obj
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
deepCopy (obj) {
|
||||||
|
if (obj === null || typeof obj !== 'object') {
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
const copy = Array.isArray(obj) ? [] : {}
|
||||||
|
|
||||||
|
Object.keys(obj).forEach(key => {
|
||||||
|
copy[key] = this.deepCopy(obj[key])
|
||||||
|
})
|
||||||
|
|
||||||
|
return copy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Form.routes = {}
|
||||||
|
Form.errorMessage = 'Something went wrong. Please try again.'
|
||||||
|
Form.ignore = ['isBusy', 'isDisabled', 'errors', 'originalData']
|
||||||
|
|
||||||
|
export default Form
|
51
resources/js_vue3/components/formElements/FormButtons.vue
Normal file
51
resources/js_vue3/components/formElements/FormButtons.vue
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
showCancelButton: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
isBusy: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
isDisabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
caption: {
|
||||||
|
type: String,
|
||||||
|
default: 'Submit'
|
||||||
|
},
|
||||||
|
cancelLandingView: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: 'is-link'
|
||||||
|
},
|
||||||
|
submitId: {
|
||||||
|
type: String,
|
||||||
|
default: 'btnSubmit'
|
||||||
|
},
|
||||||
|
cancelId: {
|
||||||
|
type: String,
|
||||||
|
default: 'btnCancel'
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="field is-grouped">
|
||||||
|
<div class="control">
|
||||||
|
<VueButton :id="submitId" :color="color" :isLoading="isBusy" :disabled="isDisabled" >
|
||||||
|
{{ caption }}
|
||||||
|
</VueButton>
|
||||||
|
</div>
|
||||||
|
<div class="control" v-if="showCancelButton">
|
||||||
|
<RouterLink :id="cancelId" :to="{ name: cancelLandingView }" class="button is-text">
|
||||||
|
{{ $t('commons.cancel') }}
|
||||||
|
</RouterLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
141
resources/js_vue3/components/formElements/FormErrors.js
vendored
Normal file
141
resources/js_vue3/components/formElements/FormErrors.js
vendored
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
|
||||||
|
export default class Errors {
|
||||||
|
/**
|
||||||
|
* Create a new error bag instance.
|
||||||
|
*/
|
||||||
|
constructor () {
|
||||||
|
this.errors = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the errors object or field error messages.
|
||||||
|
*
|
||||||
|
* @param {Object|String} field
|
||||||
|
* @param {Array|String|undefined} messages
|
||||||
|
*/
|
||||||
|
set (field, messages) {
|
||||||
|
if (typeof field === 'object') {
|
||||||
|
this.errors = field
|
||||||
|
} else {
|
||||||
|
this.set({ ...this.errors, [field]: arrayWrap(messages) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the errors.
|
||||||
|
*
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
all () {
|
||||||
|
return this.errors
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if there is an error for the given field.
|
||||||
|
*
|
||||||
|
* @param {String} field
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
has (field) {
|
||||||
|
return this.errors.hasOwnProperty(field)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if there are any errors for the given fields.
|
||||||
|
*
|
||||||
|
* @param {...String} fields
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
hasAny (...fields) {
|
||||||
|
return fields.some(field => this.has(field))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if there are any errors.
|
||||||
|
*
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
any () {
|
||||||
|
return Object.keys(this.errors).length > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the first error message for the given field.
|
||||||
|
*
|
||||||
|
* @param String} field
|
||||||
|
* @return {String|undefined}
|
||||||
|
*/
|
||||||
|
get (field) {
|
||||||
|
if (this.has(field)) {
|
||||||
|
return this.getAll(field)[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the error messages for the given field.
|
||||||
|
*
|
||||||
|
* @param {String} field
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
getAll (field) {
|
||||||
|
return arrayWrap(this.errors[field] || [])
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the error message for the given fields.
|
||||||
|
*
|
||||||
|
* @param {...String} fields
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
only (...fields) {
|
||||||
|
const messages = []
|
||||||
|
|
||||||
|
fields.forEach(field => {
|
||||||
|
const message = this.get(field)
|
||||||
|
|
||||||
|
if (message) {
|
||||||
|
messages.push(message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return messages
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the errors in a flat array.
|
||||||
|
*
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
flatten () {
|
||||||
|
return Object.values(this.errors).reduce((a, b) => a.concat(b), [])
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear one or all error fields.
|
||||||
|
*
|
||||||
|
* @param {String|undefined} field
|
||||||
|
*/
|
||||||
|
clear (field) {
|
||||||
|
const errors = {}
|
||||||
|
|
||||||
|
if (field) {
|
||||||
|
Object.keys(this.errors).forEach(key => {
|
||||||
|
if (key !== field) {
|
||||||
|
errors[key] = this.errors[key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.set(errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the given value is not an array, wrap it in one.
|
||||||
|
*
|
||||||
|
* @param {Any} value
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
function arrayWrap (value) {
|
||||||
|
return Array.isArray(value) ? value : [value]
|
||||||
|
}
|
88
resources/js_vue3/components/formElements/FormField.vue
Normal file
88
resources/js_vue3/components/formElements/FormField.vue
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
<template>
|
||||||
|
<div class="field" :class="{ 'pt-3' : hasOffset }">
|
||||||
|
<label :for="inputId" class="label" v-html="label"></label>
|
||||||
|
<div class="control">
|
||||||
|
<input
|
||||||
|
:disabled="isDisabled"
|
||||||
|
:id="inputId"
|
||||||
|
:type="inputType"
|
||||||
|
class="input"
|
||||||
|
v-model="form[fieldName]"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
v-bind="$attrs"
|
||||||
|
v-on:change="$emit('field-changed', form[fieldName])"
|
||||||
|
:maxlength="this.maxLength"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<FieldError :form="form" :field="fieldName" />
|
||||||
|
<p class="help" v-html="help" v-if="help"></p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { useIdGenerator } from '../../composables/helpers'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'FormField',
|
||||||
|
inheritAttrs: false,
|
||||||
|
|
||||||
|
setup(props) {
|
||||||
|
const { inputId } = useIdGenerator(props.inputType, props.fieldName)
|
||||||
|
return { inputId }
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
|
||||||
|
fieldName: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
|
||||||
|
inputType: {
|
||||||
|
type: String,
|
||||||
|
default: 'text'
|
||||||
|
},
|
||||||
|
|
||||||
|
form: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
|
||||||
|
help: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
|
||||||
|
hasOffset: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
|
||||||
|
isDisabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
|
||||||
|
maxLength: {
|
||||||
|
type: Number,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
139
resources/js_vue3/components/formElements/FormPasswordField.vue
Normal file
139
resources/js_vue3/components/formElements/FormPasswordField.vue
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
<template>
|
||||||
|
<div class="field" :class="{ 'pt-3' : hasOffset }">
|
||||||
|
<label :for="inputId" class="label" v-html="label"></label>
|
||||||
|
<div class="control has-icons-right">
|
||||||
|
<input
|
||||||
|
:disabled="isDisabled"
|
||||||
|
:id="inputId"
|
||||||
|
:type="currentType"
|
||||||
|
class="input"
|
||||||
|
v-model="form[fieldName]"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
v-bind="$attrs"
|
||||||
|
v-on:change="$emit('field-changed', form[fieldName])"
|
||||||
|
v-on:keyup="checkCapsLock"
|
||||||
|
/>
|
||||||
|
<span v-if="currentType == 'password'" role="button" id="btnTogglePassword" tabindex="0" class="icon is-small is-right is-clickable" @keyup.enter="setFieldType('text')" @click="setFieldType('text')" :title="$t('auth.forms.reveal_password')">
|
||||||
|
<font-awesome-icon :icon="['fas', 'eye-slash']" />
|
||||||
|
</span>
|
||||||
|
<span v-else role="button" id="btnTogglePassword" tabindex="0" class="icon is-small is-right is-clickable" @keyup.enter="setFieldType('password')" @click="setFieldType('password')" :title="$t('auth.forms.hide_password')">
|
||||||
|
<font-awesome-icon :icon="['fas', 'eye']" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p class="help is-warning" v-if="hasCapsLockOn" v-html="$t('auth.forms.caps_lock_is_on')" />
|
||||||
|
<FieldError :form="form" :field="fieldName" />
|
||||||
|
<p class="help" v-html="help" v-if="help"></p>
|
||||||
|
<div v-if="showRules" class="columns is-mobile is-size-7 mt-0">
|
||||||
|
<div class="column is-one-third">
|
||||||
|
<span class="has-text-weight-semibold">{{ $t("auth.forms.mandatory_rules") }}</span><br />
|
||||||
|
<span class="is-underscored" id="valPwdIsLongEnough" :class="{'is-dot' : IsLongEnough}"></span>{{ $t('auth.forms.is_long_enough') }}<br/>
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<span class="has-text-weight-semibold">{{ $t("auth.forms.optional_rules_you_should_follow") }}</span><br />
|
||||||
|
<span class="is-underscored" id="valPwdHasLowerCase" :class="{'is-dot' : hasLowerCase}"></span>{{ $t('auth.forms.has_lower_case') }}<br/>
|
||||||
|
<span class="is-underscored" id="valPwdHasUpperCase" :class="{'is-dot' : hasUpperCase}"></span>{{ $t('auth.forms.has_upper_case') }}<br/>
|
||||||
|
<span class="is-underscored" id="valPwdHasSpecialChar" :class="{'is-dot' : hasSpecialChar}"></span>{{ $t('auth.forms.has_special_char') }}<br/>
|
||||||
|
<span class="is-underscored" id="valPwdHasNumber" :class="{'is-dot' : hasNumber}"></span>{{ $t('auth.forms.has_number') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { useIdGenerator } from '../../composables/helpers'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'FormPasswordField',
|
||||||
|
inheritAttrs: false,
|
||||||
|
|
||||||
|
setup(props) {
|
||||||
|
const { inputId } = useIdGenerator('password', props.fieldName)
|
||||||
|
return { inputId }
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentType: this.inputType,
|
||||||
|
hasCapsLockOn: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
hasLowerCase() {
|
||||||
|
return /[a-z]/.test(this.form[this.fieldName])
|
||||||
|
},
|
||||||
|
hasUpperCase() {
|
||||||
|
return /[A-Z]/.test(this.form[this.fieldName])
|
||||||
|
},
|
||||||
|
hasNumber() {
|
||||||
|
return /[0-9]/.test(this.form[this.fieldName])
|
||||||
|
},
|
||||||
|
hasSpecialChar() {
|
||||||
|
return /[^A-Za-z0-9]/.test(this.form[this.fieldName])
|
||||||
|
},
|
||||||
|
IsLongEnough() {
|
||||||
|
return this.form[this.fieldName].length >= 8
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
|
||||||
|
fieldName: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
|
||||||
|
inputType: {
|
||||||
|
type: String,
|
||||||
|
default: 'password'
|
||||||
|
},
|
||||||
|
|
||||||
|
form: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
|
||||||
|
help: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
|
||||||
|
hasOffset: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
|
||||||
|
isDisabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
|
||||||
|
showRules: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
checkCapsLock(event) {
|
||||||
|
this.hasCapsLockOn = event.getModifierState('CapsLock') ? true : false
|
||||||
|
},
|
||||||
|
|
||||||
|
setFieldType(event) {
|
||||||
|
if (this.currentType != event) {
|
||||||
|
this.currentType = event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
34
resources/js_vue3/composables/helpers.js
vendored
Normal file
34
resources/js_vue3/composables/helpers.js
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// import { ref } from 'vue'
|
||||||
|
|
||||||
|
export function useIdGenerator(fieldType, fieldName) {
|
||||||
|
let prefix
|
||||||
|
fieldName = fieldName.toString()
|
||||||
|
|
||||||
|
switch (fieldType) {
|
||||||
|
case 'text':
|
||||||
|
prefix = 'txt'
|
||||||
|
break
|
||||||
|
case 'button':
|
||||||
|
prefix = 'btn'
|
||||||
|
break
|
||||||
|
case 'email':
|
||||||
|
prefix = 'eml'
|
||||||
|
break
|
||||||
|
case 'password':
|
||||||
|
prefix = 'pwd'
|
||||||
|
break
|
||||||
|
case 'radio':
|
||||||
|
prefix = 'rdo'
|
||||||
|
break
|
||||||
|
case 'label':
|
||||||
|
prefix = 'lbl'
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
prefix = 'txt'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
inputId: prefix + fieldName[0].toUpperCase() + fieldName.toLowerCase().slice(1)
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<responsive-width-wrapper>
|
<ResponsiveWidthWrapper>
|
||||||
<h1 class="title has-text-grey-dark" v-html="title" v-if="title"></h1>
|
<h1 class="title has-text-grey-dark" v-html="title" v-if="title"></h1>
|
||||||
<div id="punchline" v-if="punchline" class="block" v-html="punchline"></div>
|
<div id="punchline" v-if="punchline" class="block" v-html="punchline"></div>
|
||||||
<slot />
|
<slot />
|
||||||
</responsive-width-wrapper>
|
</ResponsiveWidthWrapper>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
Loading…
Reference in New Issue
Block a user