As the header describes, I would like to append input elements to some fieldset in my form.
I build a staged form with 3 fieldsets which appear only when "Next" button is clicked.
Now in fieldset number 3 I want to input elements based on keys I'm extracting from a external json object.
Data:
data: () => ({
currentPage: 0,
brand: '',
platform: '',
affiliate: '',
fieldsetThree: document.getElementById("fieldset3"),
}),
Fieldset(only appears when currentPage value is 2):
<div v-if="currentPage === 2">
<fieldset id="fieldset3">
<h2 class="fs-title">API Credentials</h2>
<h3 class="fs-subtitle">Step 3- Add any parameter for the integration</h3>
</fieldset>
</div>
Function to append the input fields after Stage 2 is done:
appendFields() {
let check = json[this.platform];
console.log(this.fieldsetThree);
for (const [key, value] of Object.entries(check)) {
let inputfield = document.createElement("input");
this.inputfield.setAttribute("type", "text");
this.inputfield.setAttribute("name",`${key}`);
this.inputfield.setAttribute("placeholder",`${key}`);
console.log("Input FIeld \n", this.inputfield)
this.fieldsetThree.appendChild(this.inputfield);
}
//Build it before.
},
The goal is to create an input field for every key in the JSON, I will add the json format for example:
"exmaple": {
"Username": "",
"Password": "",
"AffiliateID": "",
"GI": "",
"CI": "",
"freeTextArea": ""
},
FUll code(without Template):
export default {
data: () => ({
currentPage: 0,
brand: '',
platform: '',
affiliate: '',
}),
computed: {
platformData: function () {
return json[this.platform];
}
},
methods: {
isNextClicked() {
var nextStage = this.currentPage;
this.currentPage++;
console.log("CurrentPage =>", this.currentPage);
$("#progressbar li").eq($("fieldset").index(nextStage)).addClass("active");
return this.currentPage;
},
isPreviousClicked() {
this.currentPage--;
var previousStage = this.currentPage;
console.log("CurrentPage at decrease =>", this.currentPage);
$("#progressbar li").eq($("fieldset").index(previousStage)).removeClass("active");
return this.currentPage;
},
appendFields() {
// let check = json[this.platform];
// console.log(this.fieldsetThree);
// for (const [key, value] of Object.entries(check)) {
//
// let inputfield = document.createElement("input");
// this.inputfield.setAttribute("type", "text");
// this.inputfield.setAttribute("name",`${key}`);
// this.inputfield.setAttribute("placeholder",`${key}`);
// console.log("Input FIeld \n", this.inputfield)
// this.fieldsetThree.appendChild(this.inputfield);
// }
//Build it before.
},
nextPlusappend() {
//this.appendFields();
this.isNextClicked();
}
}
}
If you work out the structure you want to keep your data, then you don't need to mess with DOM elements: just add the JSON to the data() & let Vue do its thing.
So, if you take the JSON data structure as the base for the form fields & fieldsets, then you can just add it to the data item holding the form input fields:
const example = {
"Username": "",
"Password": "",
"AffiliateID": "",
"GI": "",
"CI": "",
"freeTextArea": ""
}
Vue.component("formFieldset", {
props: ["fields"],
methods: {
handleInput(val, key) {
this.$emit("update:fields", { key, val })
},
},
template: `
<div>
<label
v-for="(val, key) in fields"
:key="key"
>
{{ key }}:
<input
type="text"
:placeholder="key"
#input="(e) => handleInput(e.target.value, key)"
/>
</label>
<hr>
</div>
`
})
new Vue({
el: "#app",
data() {
return {
fieldsets: [
{
field1_1: "",
field1_2: "",
},
{
field2_1: "",
field2_2: "",
},
],
}
},
// just to add the items later to the data():
mounted() {
this.fieldsets.push(example)
},
methods: {
handleUpdateFields({ key, val, idx }) {
this.fieldsets[idx][key] = val
},
},
template: `
<div>
Putting out the data():<br />
{{ fieldsets }}
<hr>
<form>
<form-fieldset
v-for="(fieldset, i) in fieldsets"
:key="i"
:fields="fieldset"
#update:fields="(e) => handleUpdateFields({...e, idx: i})"
/>
</form>
</div>
`
})
label {
display: block;
padding: 8px 16px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>
Related
I have an object in my Vue instance with the name of items
<script>
export default {
data() {
return {
selected: "",
items: {
item1: [{ selected: "", inputType: "", inputTarget: "" }],
item2: [{ selected: "", inputType: "", inputTarget: "" }]
},
textarea: ""
};
},
methods: {
selectboxAction(index) {
this.items.item1.forEach(val => {
if (val.selected.toLowerCase() === "file") {
this.$refs.inputData[index].type = "file";
} else {
this.$refs.inputData[index].type = "text";
}
});
}
}
};
</script>
how can I fetch an array of it, I want to put some condition over every item, but it could have item more than 2 items, maybe in the future it reaches 100
as you can see in selectboxAction method, I was able to fetch only one item which is item1 in this case
how can I fetch all of the arrays from items, not just one item1
I suggest you format your data using a computed getter and use Object.keys as others have suggested.
get itemsArray() {
return Object.keys(this.items).map((key) =>
return this.items[key];
});
}
Instead of naming your 'items' properties 'item1', 'item2', etc, it would be better make 'items' an array and add an 'id' property to each 'items' object:
data() {
return {
selected: "",
items: [
{ id: 1, selected: "", inputType: "", inputTarget: "" },
{ id: 2, selected: "", inputType: "", inputTarget: "" }
],
textarea: ""
};
},
you can do something like
methods: {
selectboxAction(index) {
Object.keys(this.items).forEach(val => {
this.items[val].forEach(item => {
if (item.selected.toLowerCase() === "file") {
this.$refs.inputData[index].type = "file";
} else {
this.$refs.inputData[index].type = "text";
}
});
});
}
}
I am using vuesax 4 and I have select options in my form but each time I select an option it submits my form! how to prevent that of happening?
Code
HTML
<form
class="mt-2"
ref="form"
:model="form"
enctype="multipart/form-data"
>
<vs-select
:key="tags.length"
filter
:label="$t('posts.tags')"
v-model="form.tags"
multiple
>
<vs-option
v-for="tag in tags"
:key="tag.id"
:label="tag.name"
:value="tag.id"
>
{{ tag.name }}
</vs-option>
</vs-select>
<vs-button #click="onSubmit" native-type="submit" gradient>
{{ $t("posts.save") }}
</vs-button>
</form>
SCRIPT
data() {
return {
categories: [],
tags: [],
form: {
name: "",
slug: "",
image: "",
icon: "",
body: "",
online: "",
template: "",
quote: "",
video: "",
tags: [],
categories: [],
metaTags: [],
metaDescription: "",
},
};
},
mounted() {
this.fetchTags();
},
methods: {
fetchTags() {
axios
.get("/api/admin/tags", {
headers: {
Authorization: localStorage.getItem("access_token"),
},
})
.then((response) => {
this.tags = response.data.data;
})
.catch(function (error) {
console.log("error", error);
});
},
onSubmit(e) {
e.preventDefault();
let formData = new FormData();
formData.append("name", this.form.name);
formData.append("slug", this.form.slug);
formData.append("image", this.form.image);
formData.append("icon", this.form.icon);
formData.append("body", this.form.body);
formData.append("online", this.form.online);
formData.append("template", this.form.template);
formData.append("quote", this.form.quote);
formData.append("video", this.form.video);
formData.append("tags", this.form.tags);
formData.append("categories", this.form.categories);
formData.append("metas", this.form.metaTags);
formData.append("metas", this.form.metaDescription);
axios
.post("/api/admin/posts/add", formData, {
headers: {
Authorization: localStorage.getItem("access_token"),
},
})
.then((res) => {
this.$router.push({ name: "adminPosts" });
this.form = {
name: "",
slug: "",
image: "",
icon: "",
body: "",
online: "",
template: "",
quote: "",
video: "",
tags: [],
categories: [],
metaTags: [],
metaDescription: "",
};
const noti = this.$vs.notification({
position: "top-right",
color: "success",
icon: "<i class='fas fa-check'></i>",
title: "Done!",
text: res.data.message,
});
})
.catch((error) => {
var errors = error.response.data;
let errorsHtml = "<ol>";
$.each(errors.errors, function (k, v) {
errorsHtml += "<li>" + v + "</li>";
});
errorsHtml += "</ol>";
const noti = this.$vs.notification({
position: "top-right",
color: "danger",
icon: "<i class='fas fa-bug'></i>",
title: "Oops!",
text: errorsHtml,
});
});
},
}
Any idea?
In the Vuesax 4 <vs-select> docs, there is no <form> tag and perhaps you shouldn't have one either. (In a SPA, you don't typically submit the form using the browser's built in form handling anyway, and you are not using it like that either, nor the ref or the model.)
If you still want to continue using the <form> tag, you can use the #submit.prevent modifier:
<form
class="mt-2"
ref="form"
:model="form"
enctype="multipart/form-data"
#submit.prevent
>
I have a Vue component receiving an array of 'items' from its parent.
I've sorted them into categories, two 'items' in each category:
computed: {
// sort items into categories
glass: function() {
return this.items.filter(i => i.category === "glass").slice(0, 2);
},
ceramics:
// etc...
I need to place both items in categories.items to then pass them as props to another component:
data() {
return {
categories: [
{ name: "Glass", sort: "glass", items: {} },
{ name: "Ceramics", sort: "ceramics", items: {} },
{ name: "Brass", sort: "brass", items: {} },
{ name: "Books/Comics", sort: "books", items: {} },
{ name: "Collectibles", sort: "collectibles", items: {} },
{ name: "Pictures", sort: "pictures", items: {} },
{ name: "Other", sort: "other", items: {} }
]
};
},
When I use created or mounted nothing is passed through, when I use beforeDestroy or destroy and console.log the results it works fine, but, they're of no use when exiting the page.
The 'items' are from an Axios GET request, could this be why?
GET request from parent component:
methods: {
fetchItems() {
// items request
let uri = "http://localhost:8000/api/items";
this.axios.get(uri).then(response => {
// randomize response
for (let i = response.data.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[response.data[i], response.data[j]] = [
response.data[j],
response.data[i]
];
}
this.items = response.data;
});
}
},
Passing props to child component:
<div
class="items-container"
v-for="category in categories"
:key="category.name"
>
<router-link :to="'category.link'" class="category-names-home-link">
<h2 class="category-names-home">{{ category.name }}</h2>
</router-link>
<router-link
:to="'category.link'"
class="home-view-all"
#mouseover.native="expand"
#mouseout.native="revert"
>View All...</router-link
>
<div class="items">
<!-- Pass to child component as props: -->
<SubItem :item="categories.items" />
<SubItem :item="categories.items" />
</div>
</div>
Don't bother adding the items to the categories, keep them separate
Instead of multiple computeds, use one computed object hash to store all the filtered sets:
computed: {
filtered() {
if (!this.items) return null;
const filtered = {};
this.items.forEach(item => {
if (filtered[item.category]) {
filtered[item.category].push(item);
} else {
filtered[item.category] = [item];
}
});
return filtered;
}
}
Result:
{
'Glass': [ ... ],
'Ceramic': [ ... ]
...
}
In the template:
<div>
<div v-for="category in categories" :key="category.name">
<div class="items" v-for="item in filtered[category.name]">
<SubItem :item="item" />
</div>
</div>
</div>
You can use a v-if in the parent to prevent displaying anything until the data is loaded:
<display v-if="items" :items="items"></display>
Here is a demo
In my vue-app I have an array of job-postings, which have different states, such as "active", "rejected", "draft", "not_active" etc. Now I have a TabMenu: All Jobs, Drafts and To Be Approved. Each of those MenuTabs, have their own Dropdown menu, where you are supposed to filter the jobpostings. I've realized that this feature is more complex than expected, or maybe I have spend too much time with the issues, but for some reason, I cannot manage, to show "all" for the individual MenuTab. For example, when I click on the "To Be Approved" MenuTab, I want to see all the jobpostings, with the status "Not approved" and "Rejected" (See data below in the code).
So my question is, how to solve this properly? Does the job-posting data object need to have a category too?
Any help is most welcome!
So, here is my component:
<template>
<div>
<div class="tabs">
<ul>
<li v-for="(tab, index) in menuTabs” :key="tab.id" :class="{ 'active': activeTab === index }"
#click="toggleList(tab, index)” >
<span>{{tab.label}}</span>
</li>
</ul>
</div>
<div class="dropdown has-prepend col-8" :class="{ active: isOpen }">
<div :class="{ active: isOpen }" class="dropdown-select" #click="toggle">
{{ selectedOption }}
<i class="icon-chevron_down" />
</div>
<div class="dropdown-options" v-show="isOpen">
<div class="option" v-for="tab in dropDownTabs" #click="select(tab)" :key="tab.id">
<span>{{ tab.status }}</span>
</div>
</div>
</div>
<div class="block">
<DataTable :data="filteredData" :columns="tableColumns" :filter="search" />
</div>
</div>
</template>
import DataTable from '../../snippets/DataTable';
export default {
components: { DataTable },
data() {
return {
isOpen: false,
search: "",
tableData: [
{
id: 1,
title: "Salesperson",
publish_date: "2019-07-10",
status: "active",
applicants: 23,
expiration_date: "2020-02-21"
},
{
id: 2,
title: "Developer",
publish_date: "2019-11-12",
status: "not_active",
applicants: 111,
expiration_date: "2020-02-21"
},
{
id: 3,
title: "Freelanceer",
publish_date: "2019-06-10",
status: "need_approval",
applicants: 222,
expiration_date: "2020-01-10"
},
{
id: 4,
title: "Construction worker",
publish_date: "2019-12-06",
status: "active",
applicants: 76,
expiration_date: "2020-03-15"
},
{
id: 5,
title: "IT support”
publish_date: "2019-11-20",
status: "draft",
applicants: 103,
expiration_date: "2020-04-31"
},
],
menuTabs: [
{
label: "All jobs",
options: [
{
status: "all",
},
{
status: "active",
},
{
status: "not_active"
}
]
},
{
label: "Drafts",
options: [
{
status: "all"
},
{
status: "draft"
}
]
},
{
label: "To Be Approved",
options: [
{
status: "all",
},
{
status: "need_approval",
},
{
status: "rejected"
}
]
},
],
dropDownTabs: [],
selectedOption: "",
selectedTabCategory: "",
category: "",
activeTab: "",
tableColumns: [
"id",
"title",
"publish_date",
"status",
"applicants",
"expiration_date"
]
}
},
computed: {
filteredData() {
let status = this.selectedOption;
let category = this.category;
let filtered = this.tableData.filter(data => {
if (status == "all") {
return data;
}
return data.status === status;
});
return filtered;
}
},
methods: {
toggleList(tab, index) {
this.category = tab.options[0].category;
this.selectedTabCategory = tab;
let currentTabOptions = this.selectedTabCategory.options;
this.clearDropDown();
this.selectedOption = currentTabOptions[0].status;
currentTabOptions.forEach(option => {
this.dropDownTabs.push(option);
});
this.activeTab = index;
},
toggle() {
this.isOpen = !this.isOpen;
},
select(tab) {
this.selectedOption = tab.status;
let category = tab.category;
let filtered = this.tableData.filter(data => {
return data.status === this.selectedOption;
});
this.isOpen = false;
return filtered;
},
clearDropDown() {
this.dropDownTabs = [];
}
},
created() {},
mounted() {
this.selectedOption = this.menuTabs[0].options[0].status;
this.selectedTabCategory = this.menuTabs[0].label;
this.category = this.menuTabs[0].options[0].category;
let defaultOptions = this.menuTabs[0].options;
defaultOptions.forEach(option => {
this.dropDownTabs.push(option);
});
this.activeTab = 0;
}
};
I am not sure if it will help you at all. However I will try anyway.
You should store the selected tab when you click on it. Then filter the this.tableData based on the selected tab options. Also you will need map the tab option options to array of strings, so you can check if the posting status is in there.
methods: {
toggleList (tab, index) {
this.selectedTabObject = tab
// rest of your code...
}
},
computed: {
filteredData () {
return this.tableData.filter(data => {
const states = this.selectedTabObject.options.map(opt => opt.status)
return states.includes(data.status)
})
}
}
Also I have created simple fiddle to mimic your problem.
https://jsfiddle.net/3hqnp7u2/7/
I had a very long form which around 20 different fields and I displayed those input area using map function. I want to valid the input data when I click the submit button and jump to the corresponding input required box.
const ReportFields = [
{
title: "Report Title*",
field: "report_title",
type: "text",
required: true
},
{
title: "Submitting Agency*",
field: "submitting_agency",
type: "text",
required: true
},
{
title: "Division*",
field: "division",
type: "select",
required: true
},
{
title: "Committee*",
field: "committee",
type: "select",
required: true
},
{
title: "Assigned Contact*",
field: "assigned_contact",
type: "select",
required: true
},
{
title: "Other Recipients",
field: "other_recipients",
type: "text",
required: false
}];
class App extends Component {
state = {
report: {
report_title: "",
submitting_agency: "",
division: "",
committee: "",
assigned_contact: "",
other_recipients: ""
},
errorMessage: "",
refs: {}
}
componentDidMount() {
this.registerRefs();
}
registerRefs = () => {
const refs = ReportFields.reduce((acc, current) => {
const ref = React.createRef();
acc[current.field] = ref;
return acc;
}, {});
this.setState({ refs });
}
onSubmit = (e) => {
e.preventDefault();
for (let i = 0; i < ReportFields.length; i++) {
const curt = ReportFields[i];
if (curt.required && this.state.report[curt.field] === "") {
this.setState({errorMessage: `${curt.title} cannot be empty!`});
this.state.refs[curt.field].current.focus();
break;
}
}
}
render() {
const display = ReportFields.map((field, idx) => {
return (
<div key={idx}>
<p>{field.title}</p>
<input
type={field.type}
onChange={(e) => {
this.setState({
report: {...this.state.report, [field.field]: e.target.value}
})
}}
ref={this.state.refs[field.field]}
></input>
</div>
);
})
return (
<div className="App">
{display}
<input type="button" value="submit" onClick={this.onSubmit}/>
</div>
);
}
}
export default App;
I tried to use react refs but it doesn't work, any idea?
Also, I am actually using these content in react modal, will this be one of the reason why it doesn't work?
Ok here is a solution I know who can work but I don't say it's the best one. A working example here https://codesandbox.io/s/94v4r6w7kr. As you can see when you click submit you jump to password input.
How do that work ? First as you can see we need a way to save all the ref we gonna create. I save it in the state refs here. The way that work is a loop over each field and for each one I createRef and add this to an object. I use this object inside the state. When you want to use it after that, you then can call this.state.refs[thenameoftheinput].current.focus().
This is an example, and I let you make it work with your own data. But I hope that can give you an idea :)
const ReportFields = [
{
title: "Report Title*",
field: "report_title",
type: "text",
required: true
},
{
title: "Submitting Agency*",
field: "submitting_agency",
type: "text",
required: true
},
{
title: "Division*",
field: "division",
type: "select",
required: true
},
{
title: "Committee*",
field: "committee",
type: "select",
required: true
},
{
title: "Assigned Contact*",
field: "assigned_contact",
type: "select",
required: true
},
{
title: "Other Recipients",
field: "other_recipients",
type: "text",
required: false
}
];
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
refs: {}
};
}
componentDidMount() {
this.registerRefs();
}
registerRefs = () => {
const refs = ReportFields.reduce((acc, current) => {
const ref = React.createRef();
acc[current.field] = ref;
return acc;
}, {});
this.setState({ refs });
};
focusTextInput = () => {
this.state.refs.division.current.focus();
};
render() {
const inputs = ReportFields.map(el => {
return <input placeholder={el.title} ref={this.state.refs[el.field]} />;
});
return (
<div>
<form>
{inputs}
<input type="button" value="submit" onClick={this.focusTextInput} />
</form>
</div>
);
}
}
Iterate through all fields and create a separate ref for each one of them. Use a unique identifier (as a suggestion - the name property) to access the ref later.
class App extends Component {
constructor(props) {
super(props);
this.focusTextInput = this.focusTextInput.bind(this);
// Fields
this.ReportFields = [
{
type: "text",
name: "firstname",
title: "First Name"
},
{
type: "text",
name: "lastname",
title: "Last Name"
}
];
this.inputRefs = this.ReportFields.reduce((acc, field) => ({
...acc,
[field.name]: React.createRef()
}), {});
}
state = {
a: {
b: "",
c: "",
d: "",
e: "",
f: "",
g: "",
h: "",
i: "",
j: "",
k: "",
l: "",
m: "",
n: "",
o: "",
p: "",
q: "",
r: "",
s: "",
t: "",
u: "",
v: "",
w: "",
x: "",
y: ""
},
errorMessage: ""
};
focusTextInput() {
// Focus on the input you wish, in this case "firstname"
console.log(this.inputRefs["firstname"].current.focus());
}
render() {
const display = this.ReportFields.map((field, idx) => {
return (
<div key={idx}>
<p>{field.title}</p>
<input
type={field.type}
onChange={e => {
this.setState({
report: { ...this.state.report, [field.field]: e.target.value }
});
}}
ref={this.inputRefs[field.name]}
/>
</div>
);
});
return (
<div className="App">
{display}
<input type="button" value="submit" onClick={this.focusTextInput} />
</div>
);
}
}