How to change text inputs into tags, in an existing Vue 3 form, using a Tag Input Component? - forms

I have an existing form in a Vue 3 project that posts successfully to a Cloud Firestore database.
How do I use change two of the type="text" input fields into tag inputs, using a Tag Input Component like this? Here's a link to it on CodeSandbox
The standalone tutorial is very clear and works fine. What I'm struggling with is how to incorporate that into an existing Vue 3 form and continue posting everything to the Firestore database.
I stripped-away styles and many other input fields from the "base" code below:
<template>
<form #submit.prevent="handleSubmit" >
<h3>What kind of pet?</h3>
<div class="space-y-0">
<select required name="pet_type" id="pet_type" v-model="pet_type">
<option value="">Please select</option>
<option value="cat">Cat</option>
<option value="dog">Dog</option>
<option value="wolverine">Wolverine</option>
</select>
</div>
<hr>
<div> <!-- Want to change this into the first tag input -->
<h3>Pros</h3>
<div for="petPros">What are 3 positive things about this pet?</div>
<input type="text" placeholder="separate , with , commas" id="petPros" v-model="petPros">
<label for="petPros">separate , with , commas</label>
</div>
<hr>
<div>
<h3>Cons</h3> <!-- Want to change this into a second tag input -->
<div for="petCons">And what are 3 negative things about this pet?</div>
<input type="text" placeholder="separate , with , commas" id="petPros" v-model="petCons">
<label for="petCons">separate , with , commas</label>
</div>
<hr>
<h3>Privacy</h3>
<div>
<div>
<input type="radio" id="Fullpublic" value="Fullpublic" name="reviewPrivacy" required v-model="reviewPrivacy">
<label class="text-base inline-flex ml-4 align-bottom" for="Fullpublic">Public</label>
</div>
<div>
<input type="radio" id="keepFullyPrivate" value="keepFullyPrivate" name="reviewPrivacy" required v-model="reviewPrivacy">
<label class="text-base inline-flex ml-4 align-bottom" for="keepFullyPrivate">Private</label>
</div>
</div>
<hr>
<button v-if="!isPending" >Submit</button>
<button v-else disabled>Saving...</button>
</form>
</template>
<script>
import { ref } from 'vue'
import useStorage from '#/composables/useStorage'
import useCollection from '#/composables/useCollection'
export default {
setup() {
const { filePath, url, uploadImage } = useStorage()
const { error, addDoc } = useCollection('reviews')
const { user } = getUser()
const router = useRouter()
const pet_type = ref('')
const petPros = ref('')
const petCons = ref('')
const reviewPrivacy = ref('')
const file = ref(null)
const fileError = ref(null)
const isPending = ref(false)
const handleSubmit = async () => {
if (file.value) {
isPending.value = true
await uploadImage(file.value)
const res = await addDoc({
pet_type: pet_type.value,
petPros: petPros.value,
petCons: petCons.value,
reviewPrivacy: reviewPrivacy.value,
userId: user.value.uid,
userName: user.value.displayName,
createdAt: timestamp()
})
isPending.value = false
if (!error.value) {
router.push({ name: 'ReviewDetails', params: { id: res.id }})
}
}
}
// allowed file types
const types = ['image/png', 'image/jpeg']
const handleChange = (e) => {
let selected = e.target.files[0]
console.log(selected)
if (selected && types.includes(selected.type)) {
file.value = selected
fileError.value = null
} else {
file.value = null
fileError.value = 'Please select an image file (png, jpg or jpeg)'
}
}
return { pet_type, petPros, petCons, reviewPrivacy, handleSubmit, fileError, handleChange, isPending }
}
}
</script>
<style>
</style>
Thanks for any help!

In the form, replace the two <input type="text">s with <TagInput>. Keep the v-model the same, as TabInput implements v-model.
<!-- BEFORE -->
<input type="text" placeholder="separate , with , commas" id="petPros" v-model="petPros">
<input type="text" placeholder="separate , with , commas" id="petPros" v-model="petCons">
<!-- AFTER -->
<TagInput id="petPros" v-model="petPros" />
<TagInput id="petCons" v-model="petCons" />
Locally register TagInput.vue in the form component:
import TagInput from "./TagInput.vue";
export default {
components: {
TagInput,
},
}
Change the initial values of petPros and petCons refs to arrays, as the TabInput outputs a string array of the input tag values:
// BEFORE
const petPros = ref('')
const petCons = ref('')
// AFTER
const petPros = ref([])
const petCons = ref([])
TabInput normally adds a tag upon hitting TAB, but your placeholder suggests that you want to use the comma key , instead. To do that, update the modifier in the #keydown binding:
<!-- BEFORE -->
<input v-model="newTag"
#keydown.prevent.tab="addTag(newTag)"
>
<!-- AFTER -->
<input v-model="newTag"
#keydown.prevent.,="addTag(newTag)"
>
demo

Related

How can i dynamically add an Input select element on click of a button in ember

Am creating an ember application where am in need of dynamicaly adding a select element which will have options fetched from a server. so the select elements look like this. And instead of having all dropdown boxes predefined i need to add them dynamicaly like on a click of a button like( + add more). like
and each of those drop down boxes should contain the datas that is fetched from the server. plus i need a way to get the datas from those dynamically created select fields.
my .hbs for the current drop down page is..
map.hbs
<center><h4>Map</h4></center>
<container class = "cond">
{{#each this.model.sf as |row|}}
<select class = "sel">
{{#each this.model.sf as |sf|}}
<option value = {{sf.attrname}}>{{sf.attrname}}</option>
{{/each}}
</select><br>
{{/each}}
I tried ember-dynamic-fields but its depracted and I couldnt able to use it.. and all other solutions on web or for ember way older versions.. nothing works on ember 4.6 so could anyone helpout?
Using The Platform's native FormData functionality, demo'd here.
I think we can generate any number of inputs based on input data in the following way:
Store the form's state in some variable
conditionally show further select / inputs based on the properties in that form state.
Code-wise, that'd look like this:
{{#if (dataHasValueFor "fieldName")}}
Show previously hidden field
{{/if}}
And of course the devil is in the implementation details, so, a full working example (with sample data I made up -- we can iterate on this if you want for your specific data set, just leave a comment on this post/answer).
import Component from '#glimmer/component';
import { tracked } from '#glimmer/tracking';
import { on } from '#ember/modifier';
import { get } from '#ember/helper';
// This could be your model data from your route
const DATA = {
fruits: [
'apple', 'banana', 'orange', 'mango',
'watermellon', 'avacado', 'tomato?'
],
veggies: ['cocumber', 'tomato?', 'green bean', 'kale', 'spinach'],
peppers: ['carolina reaper', 'habanero', 'jalapeƱo']
}
export default class Demo extends Component {
#tracked formData;
get categories() {
return Object.keys(DATA);
}
handleInput = (event) => {
let formData = new FormData(event.currentTarget);
let data = Object.fromEntries(formData.entries());
this.formData = data;
}
handleSubmit = (event) => {
event.preventDefault();
handleInput(event);
}
isSelected = (name, value) => this.formData?.[name] === value;
<template>
<form
{{on 'input' this.handleInput}}
{{on 'submit' this.handleSubmit}}
>
<label>
Food Category<br>
<select name="category" placeholder="Select...">
<option selected disabled>Select a food group</option>
{{#each this.categories as |name|}}
<option
value={{name}}
selected={{this.isSelected "category" name}}
>
{{name}}
</option>
{{/each}}
</select>
</label>
<hr>
{{#let (get this.formData "category") as |selectedCategory|}}
{{#if selectedCategory}}
<label>
{{selectedCategory}}<br>
<select name={{selectedCategory}}>
<option selected disabled>
Select {{selectedCategory}}
</option>
{{#each (get DATA selectedCategory) as |food|}}
<option
value={{food}}
selected={{this.isSelected selectedCategory food}}
>
{{food}}
</option>
{{/each}}
</select>
</label>
{{/if}}
{{/let}}
</form>
<hr>
FormData:
<pre>{{toJson this.formData}}</pre>
</template>
}
const toJson = (input) => JSON.stringify(input, null, 4);
This demo is interactive here, on limber.glimdown.com
Note that the syntax used here is what will be default in the upcoming Polaris Edition of Ember, and is available via ember-template-imports
Update (after comments)
Demo here
I took some liberties with the how the fields are dynamic, because I think this more easily shows the concept asked about in the question: dynamically showing fields in a form.
import Component from '#glimmer/component';
import { tracked } from '#glimmer/tracking';
import { on } from '#ember/modifier';
import { get } from '#ember/helper';
export default class Demo extends Component {
#tracked formData;
handleInput = (event) => {
let formData = new FormData(event.currentTarget);
let data = Object.fromEntries(formData.entries());
this.formData = data;
}
handleSubmit = (event) => {
event.preventDefault();
handleInput(event);
}
<template>
<form
{{on 'input' this.handleInput}}
{{on 'submit' this.handleSubmit}}
>
<div class="grid">
<label>
Name <input type="checkbox" name='hasName'>
</label>
<label>
Email <input type="checkbox" name='hasEmail'>
</label>
<label>
Alias <input type="checkbox" name='hasAlias'>
</label>
<hr>
{{#if (get this.formData 'hasName')}}
<label>
Name
<input type="text" name="name" class="border" />
</label>
{{/if}}
{{#if (get this.formData 'hasEmail')}}
<label>
Email
<input type="email" name="email" class="border" />
</label>
{{/if}}
{{#if (get this.formData 'hasAlias')}}
<label>
Alias
<input type="text" name="alias" class="border" />
</label>
{{/if}}
</div>
</form>
<hr>
FormData:
<pre>{{toJson this.formData}}</pre>
</template>
}
const toJson = (input) => JSON.stringify(input, null, 4);
And... since it seems you have a lot of fields, you may want to go as dynamic as possible:
demo here
which is the following code:
<form
{{on 'input' this.handleInput}}
{{on 'submit' this.handleSubmit}}
>
<div class="grid">
{{#each FIELDS as |field|}}
<label>
{{field}} <input type="checkbox" name='has-{{field}}'>
</label>
{{/each}}
<hr>
{{#each FIELDS as |field|}}
{{#if (get this.formData (concat 'has-' field))}}
<label>
{{field}}
<input type="text" name={{field}} class="border" />
</label>
{{/if}}
{{/each}}
</div>
</form>
I guess Simple js code did the magic of adding and retriving data.. pity of me after finding out.. And for some dynamic ember formdata the previous answer from nullvox helped out.. so here is the code
.hbs
<table class="table">
<th>
<td>Sf</td>
</th>
<th>
<td>Db</td>
</th>
<tbody id = "map">
</tbody>
</table>
<button class = "btn btn-sm btn-primary" type="button" {{action "submit"}}>Submit</button>
<button class = "btn btn-success btn-sm" onclick = {{action "get"}} type="button">Add another</button>
controller code for creating element
#action
get() {
let div = document.getElementById('map');
let tr = document.createElement('tr');
let td = document.createElement('td');
let td2 = document.createElement('td');
var select = document.createElement('select');
select.setAttribute('class', 'sfselect');
div.appendChild(tr);
tr.appendChild(td);
td.appendChild(select);
for (var i = 0; i < sf.length; i++) {
var option = document.createElement('option');
option.value = sf[i];
option.text = sf[i];
select.appendChild(option);
}
var select2 = document.createElement('select');
select2.setAttribute('class', 'dbselect');
tr.appendChild(td2);
td2.appendChild(select2);
for (var i = 0; i < db.length; i++) {
var option = document.createElement('option');
option.value = db[i];
option.text = db[i];
select2.appendChild(option);
}
}
controller code for getting data
#action submit() {
var sfattr = document.querySelectorAll('.sfselect');
var dbattr = document.querySelectorAll('.dbselect');
var sf = [];
var db = [];
console.log(sfattr.length);
let datas;
for (var i = 0; i < sfattr.length; i++) {
sf[i] = sfattr[i].value;
db[i] = dbattr[i].value;
}
let m1 = sf.toString();
let m2 = db.toString();
$.ajax({
url: 'http://localhost:8080/lorduoauth/Map',
method: 'POST',
contentType: 'application/x-www-form-urlencoded',
data: {
m1: m1,
m2: m2,
},
success: function (response) {
console.log(datas);
alert(response);
},
error: function (xhr, status, error) {
var errorMessage = xhr.status + ': ' + xhr.statusText;
alert('error' + errorMessage);
},
});
}
thus the output looks like this

How to enable required validation in vuelidate based on onChange event of select field

I have to enable required validation for the input field based on the onChange event of select field. I'm using vuelidate package for form validation in my project. Kindly provide solution to accomplish it.
My Template Code Below:
<template>
<section class="page_blk">
<form #submit="submitForm($event)" class="cryp_form">
<div class="input_ctrl_wrp">
<label for="username">Template</label>
<div class="input_select">
<select #change="getTemplate" v-model="$v.adForm.tnc.$model" name="" id="">
<option value="">Select</option>
<option value="New">New</option>
<option :value="term.idTnCTemplate"
v-for="term in termsList"
:key="term.idTnCTemplate">{{term.title}}</option>
</select>
<i class="fal fa-angle-down"></i>
</div>
</div>
<div class="input_ctrl_wrp">
<label for="username">Title</label>
<div class="input_text">
<input v-model="$v.adForm.title.$model" placeholder="" type="text">
</div>
</div>
<div class="input_ctrl_wrp">
<label for="username">Terms Of Trade</label>
<div class="input_textarea">
<textarea v-model="$v.adForm.content.$model" name="" rows="10"></textarea>
</div>
</div>
</form>
</section>
</template>
My Script Below:
<script>
import { required,requiredIf, decimal, numeric } from "vuelidate/lib/validators";
export default {
data() {
return {
adForm: {
tnc: '',
title: '',
content: '',
}
}
},
validations: {
adForm: {
tnc: {
required
},
title: {
required
},
content: {
required: requiredIf( (abc) => {
console.log('abc',abc)
return true;
})
},
schedule: {
required
}
}
},
methods: {
submitForm(e) {
},
getTemplate(e) {
}
},
mounted() {
}
}
</script>
I want to toggle the validation to required for the title and content field, if the user select new and other option from dropdown. Please provide solution to accomplish it. Thanks in advance.

Can't clear form/state after input in React.js

I have a form which ultimately will be used as the UI to make some API calls to Open weather map.
Right now when I submit the a zip code in the input field, upon submission [object Object] propagates the field like in the screen shot below.
The call to the API is working as I am getting the JSON for the correct zip code...
But shouldn't this in the handleSubmit take care of everything i.e. using Object.assign to create new state and then using form.zipcode.value = ''; to clear out the input?
Thanks in advance!!
handleSubmit(event) {
event.preventDefault();
var form = document.forms.weatherApp;
api.getWeatherByZip(this.state.zipcode).then(
function(zip) {
console.log('zip', zip);
this.setState(function() {
return {
zipcode: Object.assign({}, zip),
};
});
}.bind(this)
);
form.zipcode.value = '';
}
I have enclosed all of the component's code here.
import React, { Component } from 'react';
import * as api from '../utils/api';
import '../scss/app.scss';
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
zipcode: [],
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({
zipcode: event.target.value,
});
}
handleSubmit(event) {
event.preventDefault();
var form = document.forms.weatherApp;
api.getWeatherByZip(this.state.zipcode).then(
function(zip) {
console.log('zip', zip);
this.setState(function() {
return {
zipcode: Object.assign({}, zip),
};
});
}.bind(this)
);
form.zipcode.value = '';
}
render() {
return (
<div className="container">
<form name="weatherApp" onSubmit={this.handleSubmit}>
<h2>Open Weather App</h2>
<div className="row">
<div className="one-half column">
<label htmlFor="insertMode">Insert your location</label>
<input
name="zipcode"
className="u-full-width"
placeholder="please enter your zipcode"
type="text"
autoComplete="off"
value={this.state.zipcode}
onChange={this.handleChange}
/>
</div>
<div className="one-half column">
<label htmlFor="showMin">show minimum</label>
<input type="checkbox" />
<label htmlFor="showMax">show maximum</label>
<input type="checkbox" />
<label htmlFor="showMean">show mean</label>
<input type="checkbox" />
</div>
</div>
<div className="row">
<div className="two-half column">
<input type="submit" value="Submit" />
</div>
</div>
</form>
</div>
);
}
}
You should let react manage the changes to the DOM rather that editing it manually. As the value of your input field is already bound to this.state.zipcode to reset it just invoke this.setState({zipcode: ''}) instead of form.zipcode.value='';.

react rerendering form causes focus/blur issue on state change

I have a form in a react component that has two change handlers, one for my two draftjs textareas, and one for my other text inputs:
onChangeEditor = (editorStateKey) => (editorState) => {
this.setState({ [editorStateKey]: editorState });
}
handleInputChange(event) {
event.preventDefault();
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
In my render method I have two views that I switch between depending on which view mode I am in, read or edit:
render () {
const Editable = () => (
<div className="editor">
<form className="editor-inner">
<h3>Redigerar: Anbudsbrev</h3>
<h4>Rubrik</h4>
<input type="text" key="text01" name="title" defaultValue={this.state.title} onBlur={this.handleInputChange} />
<h4>Text 1</h4>
<RichEditor editorState={this.state.editorState1} onChange={this.onChangeEditor('editorState1')} name="text01"/>
<h4>Citat</h4>
<input type="text" key="text02" name="quote01" defaultValue={this.state.quote01} onBlur={this.handleInputChange} />
<h4>Text 2</h4>
<RichEditor editorState={this.state.editorState2} onChange={this.onChangeEditor('editorState2')} name="text02" />
<EditorFooter {...this.props} submitForm={this.saveForm} />
</form>
</div>
);
const Readable = () => (
<div>
<h1 className="header66">{this.state.title}</h1>
<div className="text66">{this.state.text01}</div>
<div className="quote100">{this.state.quote01}</div>
<div className="text66">{this.state.text02}</div>
</div>
);
return (
<div>
{ this.props.isInEditMode ? <Editable /> : <Readable /> }
</div>
);
}
When I switch between inputs in my browser I have to click twice in order to get the focus on the right input.
I suspect that this is because a change is triggered on the "blur" event of each input, causing the form to rerender because state is changed. And when the form rerenders, it goes through the { this.props.isInEditMode ? <Editable /> : <Readable /> } which causes the input to lose focus.
The problem is that I don't know how to get around this.
I solved it myself.
It turns out that it was not a good idea to place the Editable and Readable inside of my component as I did. Instead I moved them out to their own components, and it works properly now.
class Editable extends React.Component {
render() {
return (
<div className="editor">
<form className="editor-inner">
<h3>Redigerar: Anbudsbrev</h3>
<h4>Rubrik</h4>
<input type="text" name="title" defaultValue={this.props.title} onChange={this.props.handleInputChange} />
<h4>Text 1</h4>
<RichEditor editorState={this.props.editorState1} onChange={this.props.onChangeEditor('editorState1')} name="text01" />
<h4>Citat</h4>
<input type="text" name="quote01" defaultValue={this.props.quote01} onChange={this.props.handleInputChange} />
<h4>Text 2</h4>
<RichEditor editorState={this.props.editorState2} onChange={this.props.onChangeEditor('editorState2')} name="text02" />
<EditorFooter {...this.props} submitForm={this.props.saveForm} />
</form>
</div>
);
}
};
class Readable extends React.Component {
render() {
return (
<div>
<h1 className="header66">{this.props.title}</h1>
<div className="text66">{this.props.text01}</div>
<div className="quote100">{this.props.quote01}</div>
<div className="text66">{this.props.text02}</div>
</div>
);
}
};

Meteor-react inserting url into mongodb stores 'null'

I am using form component to insert data into the Mongo-Collection. When I check in terminal for stored information I do successfuly store Title and data from Select input, but have value of null for both url and file inputs.
Here is the code for handling insert method on import folder:
Meteor.methods({
'posts.insert' : function(post) {
return Posts.insert({
createdAt: new Date(),
title: post.title,
social: post.social,
link: this.link,
file: this.file
});
}
});
Here is the component code for handling form submit:
import React, { Component } from 'react';
class AddPost extends Component {
constructor(props) {
super(props);
this.state = {error: ''};
}
handleSubmit(event) {
event.preventDefault();
const title = this.refs.title.value;
const social = this.refs.social.value;
const link = this.refs.link.value;
const file = this.refs.file.value;
Meteor.call('posts.insert', {title, social, link, file});
}
render() {
return (
<div className="modal fade" id="myModal" tabIndex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div className="form-outer">
<form id='add_post' onSubmit={this.handleSubmit.bind(this)}>
<div className='form-text form-header'>
<p><strong>Hey</strong>, master<span className='error'>{this.state.error}</span></p>
<p>Lets share something new today?</p>
</div>
<input ref="title" type="text" className="form-input" placeholder="What about the title?" />
<div className='form-text form-header form-header-distance'>
<p>Where should I point the way?</p>
</div>
<select ref="social" className="form-select">
<option>Select</option>
<option>Instagram</option>
<option>Twitter</option>
</select>
<input ref="link" type="url" className="form-input" placeholder="Point the way" />
<div className='form-text form-header form-header-distance'>
<p>And what about the image?</p>
</div>
<label className="file form-file">
<input ref='file' className='form-input' type="file" id="file" />
<span className="file-custom"></span>
</label>
<button type="button" className="form-button" data-dismiss="modal">Close</button>
<button type="sumbit" className="form-button" >Save</button>
</form>
</div>
</div>
);
}
}
export default AddPost;
P.S: It's out of topic of these question, but I will do appreciate a lot if you could point me to some external resource or explain if it's possible to upload/store new images (not static from public folder) from local machine and serve them to front-end view?
Make your meteor method as below:
Meteor.methods({
'posts.insert'(post){
post.createdAt: new Date();
Posts.insert(post);
}
})