Skip to content

Commit

Permalink
v2 release
Browse files Browse the repository at this point in the history
- Password and string have there own Vue field according to there original field type
- Ability to show/hide password in forms by a 'eye' button next to the password field
- Refactored the way how data is copied (HTTPS is needed for this to work)
- Refactored Vue code to most recent Nova version, this allowed it to show the errors as it is supposed to also this added the Help text
  • Loading branch information
gldrenthe89 committed Oct 28, 2020
1 parent 346476e commit 1051da1
Show file tree
Hide file tree
Showing 15 changed files with 364 additions and 129 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
# Nova String Generation Field
A Nova field which can genarate a string upon creating or updating, this adds a string generator button and copy button to a Text or Password field.

## Screenshot
Demo with shown password.

![Demo1](docs/demo.png)

Demo with 'normal' hidden password.

![Demo1](docs/demo2.png)

## Installation:

You can install the package in to a Laravel app that uses Nova via composer:
Expand Down
2 changes: 1 addition & 1 deletion dist/js/field.js

Large diffs are not rendered by default.

Binary file added docs/demo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/demo2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions resources/js/components/Detail/PasswordGeneratorField.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<template>
<panel-item :field="field">
<p slot="value" class="text-90">
&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;&middot;
</p>
</panel-item>
</template>

<script>
export default {
props: ['resource', 'resourceName', 'resourceId', 'field'],
}
</script>

9 changes: 9 additions & 0 deletions resources/js/components/Detail/StringGeneratorField.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<template>
<panel-item :field="field" />
</template>

<script>
export default {
props: ['resource', 'resourceName', 'resourceId', 'field'],
}
</script>
9 changes: 0 additions & 9 deletions resources/js/components/DetailField.vue

This file was deleted.

130 changes: 130 additions & 0 deletions resources/js/components/Form/PasswordGeneratorField.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<template>
<default-field :field="field" :errors="errors" :show-help-text="showHelpText">
<template slot="field">
<div class="flex">
<input
:id="field.attribute"
:dusk="field.attribute"
:type="passwordFieldType"
v-model="value"
class="w-full form-control form-input form-input-bordered"
:class="errorClasses"
:placeholder="field.name"
autocomplete="new-password"
:disabled="isReadonly"
/>
<div class="my-auto ml-3 cursor-pointer" v-on:click="switchVisibility();">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" :id="field.attribute.concat('VisibilityButton')" fill="#bacad6"><path d="M15 12c0 1.654-1.346 3-3 3s-3-1.346-3-3 1.346-3 3-3 3 1.346 3 3zm9-.449s-4.252 8.449-11.985 8.449c-7.18 0-12.015-8.449-12.015-8.449s4.446-7.551 12.015-7.551c7.694 0 11.985 7.551 11.985 7.551zm-7 .449c0-2.757-2.243-5-5-5s-5 2.243-5 5 2.243 5 5 5 5-2.243 5-5z"/></svg>
</div>
<input type="button" class="btn btn-default btn-primary ml-3 cursor-pointer" value="Generate" :id="field.attribute.concat('GenerateButton')" v-on:click="generatePassword();">
<input type="button" class="btn btn-default btn-icon ml-3 cursor-pointer" value="Copy" :id="field.attribute.concat('CopyButton')" disabled="disabled" v-on:click="copyPassword();">
</div>
</template>
</default-field>
</template>

<script>
import { FormField, HandlesValidationErrors } from 'laravel-nova'
export default {
mixins: [HandlesValidationErrors, FormField],
data() {
return {
passwordFieldType: 'password'
}
},
methods: {
generatePassword() {
let chars = '';
if (this.field.exclude_rules && this.field.exclude_rules.length > 0){
for (let j = 0; j < this.field.exclude_rules.length; j++){
this.field.exclude_rules[j] = this.field.exclude_rules[j].toLowerCase();
}
if (this.field.exclude_rules.includes('symbols')
&& this.field.exclude_rules.includes('numbers')
&& this.field.exclude_rules.includes('uppercase')
&& this.field.exclude_rules.includes('lowercase')){
alert('Include at least one characters type! Symbols, Numbers, Lowercase, Uppercase');
return false;
}
if (!this.field.exclude_rules.includes('symbols')){
chars += '!@#$%^&*()-+<>'
}
if (!this.field.exclude_rules.includes('numbers')){
chars += '1234567890'
}
if (!this.field.exclude_rules.includes('uppercase')){
chars += 'ABCDEFGHIJKLMNOP'
}
if (!this.field.exclude_rules.includes('lowercase')){
chars += 'abcdefghijklmnopqrstuvwxyz'
}
} else{
chars = 'abcdefghijklmnopqrstuvwxyz!@#$%^&*()-+<>1234567890ABCDEFGHIJKLMNOP';
}
let pass = "";
let length = 10;
if (this.field.length > 0){
length = this.field.length;
}
for (let x = 0; x < length; x++) {
const i = Math.floor(Math.random() * chars.length);
pass += chars.charAt(i);
}
this.value = pass;
if (window.location.protocol === 'https:') {
document.getElementById(this.field.attribute.concat('CopyButton')).disabled = false;
}
let button = document.getElementById(this.field.attribute.concat('GenerateButton'));
button.value = 'Generated';
setTimeout(function () {
button.value = 'Regenerate'
}, 1500);
},
copyPassword() {
let fieldText = document.getElementById(this.field.attribute);
let button = document.getElementById(this.field.attribute.concat('CopyButton'));
if (fieldText.value.length > 0) {
navigator.clipboard.writeText(fieldText.value);
button.value = "Copied";
setTimeout(function () {
button.value = 'Copy'
}, 1500);
}
},
switchVisibility() {
let button = document.getElementById(this.field.attribute.concat('VisibilityButton'));
this.passwordFieldType = this.passwordFieldType === 'password' ? 'text' : 'password';
button.innerHTML = this.passwordFieldType === 'password'
? '<path d="M15 12c0 1.654-1.346 3-3 3s-3-1.346-3-3 1.346-3 3-3 3 1.346 3 3zm9-.449s-4.252 8.449-11.985 8.449c-7.18 0-12.015-8.449-12.015-8.449s4.446-7.551 12.015-7.551c7.694 0 11.985 7.551 11.985 7.551zm-7 .449c0-2.757-2.243-5-5-5s-5 2.243-5 5 2.243 5 5 5 5-2.243 5-5z"/>'
: '<path d="M11.885 14.988l3.104-3.098.011.11c0 1.654-1.346 3-3 3l-.115-.012zm8.048-8.032l-3.274 3.268c.212.554.341 1.149.341 1.776 0 2.757-2.243 5-5 5-.631 0-1.229-.13-1.785-.344l-2.377 2.372c1.276.588 2.671.972 4.177.972 7.733 0 11.985-8.449 11.985-8.449s-1.415-2.478-4.067-4.595zm1.431-3.536l-18.619 18.58-1.382-1.422 3.455-3.447c-3.022-2.45-4.818-5.58-4.818-5.58s4.446-7.551 12.015-7.551c1.825 0 3.456.426 4.886 1.075l3.081-3.075 1.382 1.42zm-13.751 10.922l1.519-1.515c-.077-.264-.132-.538-.132-.827 0-1.654 1.346-3 3-3 .291 0 .567.055.833.134l1.518-1.515c-.704-.382-1.496-.619-2.351-.619-2.757 0-5 2.243-5 5 0 .852.235 1.641.613 2.342z"/>';
},
/*
* Set the initial, internal value for the field.
*/
setInitialValue() {
this.value = this.field.value || ''
},
/**
* Fill the given FormData object with the field's internal value.
*/
fill(formData) {
formData.append(this.field.attribute, this.value || '')
},
/**
* Update the field's internal value.
*/
handleChange(value) {
this.value = value
}
}
}
</script>
151 changes: 151 additions & 0 deletions resources/js/components/Form/StringGeneratorField.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
<template>
<default-field :field="field" :errors="errors" :show-help-text="showHelpText">
<template slot="field">
<div class="flex">
<input
class="w-full form-control form-input form-input-bordered"
@input="handleChange"
:value="value"
:id="field.attribute"
:dusk="field.attribute"
v-bind="extraAttributes"
:disabled="isReadonly"
:list="`${field.attribute}-list`"
/>

<datalist
v-if="field.suggestions && field.suggestions.length > 0"
:id="`${field.attribute}-list`"
>
<option
:key="suggestion"
v-for="suggestion in field.suggestions"
:value="suggestion"
/>
</datalist>
<input type="button" class="btn btn-default btn-primary ml-3 cursor-pointer" value="Generate" :id="field.attribute.concat('GenerateButton')" v-on:click="generateString();">
<input type="button" class="btn btn-default btn-icon ml-3 cursor-pointer" value="Copy" :id="field.attribute.concat('CopyButton')" disabled="disabled" v-on:click="copyString();">
</div>
</template>
</default-field>
</template>

<script>
import { FormField, HandlesValidationErrors } from 'laravel-nova'
export default {
mixins: [HandlesValidationErrors, FormField],
computed: {
defaultAttributes() {
return {
type: this.field.type || 'text',
min: this.field.min,
max: this.field.max,
step: this.field.step,
pattern: this.field.pattern,
placeholder: this.field.placeholder || this.field.name,
class: this.errorClasses,
}
},
extraAttributes() {
const attrs = this.field.extraAttributes
return {
// Leave the default attributes even though we can now specify
// whatever attributes we like because the old number field still
// uses the old field attributes
...this.defaultAttributes,
...attrs,
}
},
},
methods: {
generateString() {
let chars = '';
if (this.field.exclude_rules && this.field.exclude_rules.length > 0){
for (let j = 0; j < this.field.exclude_rules.length; j++){
this.field.exclude_rules[j] = this.field.exclude_rules[j].toLowerCase();
}
if (this.field.exclude_rules.includes('symbols')
&& this.field.exclude_rules.includes('numbers')
&& this.field.exclude_rules.includes('uppercase')
&& this.field.exclude_rules.includes('lowercase')){
alert('Include at least one characters type! Symbols, Numbers, Lowercase, Uppercase');
return false;
}
if (!this.field.exclude_rules.includes('symbols')){
chars += '!@#$%^&*()-+<>'
}
if (!this.field.exclude_rules.includes('numbers')){
chars += '1234567890'
}
if (!this.field.exclude_rules.includes('uppercase')){
chars += 'ABCDEFGHIJKLMNOP'
}
if (!this.field.exclude_rules.includes('lowercase')){
chars += 'abcdefghijklmnopqrstuvwxyz'
}
} else{
chars = 'abcdefghijklmnopqrstuvwxyz!@#$%^&*()-+<>1234567890ABCDEFGHIJKLMNOP';
}
let string = "";
let length = 10;
if (this.field.length > 0){
length = this.field.length;
}
for (let x = 0; x < length; x++) {
const i = Math.floor(Math.random() * chars.length);
string += chars.charAt(i);
}
this.value = string;
if (window.location.protocol === 'https:') {
document.getElementById(this.field.attribute.concat('CopyButton')).disabled = false;
}
let button = document.getElementById(this.field.attribute.concat('GenerateButton'));
button.value = 'Generated';
setTimeout(function () {
button.value = 'Regenerate'
}, 1500);
this.copyString();
},
copyString() {
let fieldText = document.getElementById(this.field.attribute);
let button = document.getElementById(this.field.attribute.concat('CopyButton'));
if (fieldText.value.length > 0) {
navigator.clipboard.writeText(fieldText.value);
button.value = "Copied";
setTimeout(function () {
button.value = 'Copy'
}, 1500);
}
},
/*
* Set the initial, internal value for the field.
*/
setInitialValue() {
this.value = this.field.value || ''
},
/**
* Fill the given FormData object with the field's internal value.
*/
fill(formData) {
formData.append(this.field.attribute, this.value || '')
},
/**
* Update the field's internal value.
*/
handleChange(value) {
this.value = value
}
}
}
</script>
Loading

0 comments on commit 1051da1

Please sign in to comment.