How to get Vue.js to to dynamically add forms with django formset - vue.js

I have been struggling trying to get Vue.js to dynamically create the form while updating the ids of the form elements. I am new to Vue.js.
I have tried to have each form as a vue component, but I can't seem to update the ids, or they all seem to have the same id when inspecting with vue dev tools.
I have also tried to nest vue instances, then I am able to get the id's to be correct but then the other functionality doesn't work.
html
<body class="bg-dark">
<!--style="background-color:Black;"-->
<div class="container-fluid p-4 bg-dark text-white" style="margin-top:74px">
<div class="row">
<div class="col-md-3"></div>
<div class="col-md-7">
<div class="row" id="app">
<!-- <member-form v-for="member in members" :key="member_form_component_id"></member-form>-->
</div>
<div class="row" id="add_button_div">
<button class="btn btn-primary m-2" type="button" id="id_btn_add_row"
v-on:click="add_member">Add Member</button>
</div>
</div>
<div class="col-md-2"></div>
</div>
</div>
</body>
<script type="text/x-template" id="member-form-template">
<div class="row" id="form-div-__prefix__">
<div class="row input-group input-group-md">
<div class="col border mx-auto">
<input type="text" name="member_set-__prefix__-first_name" placeholder="First Name" autocomplete="off"
class="form-control m-2" maxlength="100" id="id_member_set-__prefix__-first_name"
v-model="member_first_name">
</div>
<div class="col border mx-auto">
<input type="text" name="member_set-__prefix__-last_name" placeholder="Last Name" autocomplete="off"
class="form-control m-2" maxlength="100" id="id_member_set-__prefix__-last_name"
v-model="member_last_name">
</div>
<div class="col border mx-auto">
<input type="date" class="form-control m-2" name="member_set-__prefix__-dob" id="id_member_set-__prefix__-dob"
v-model="member_dob" #blur="joad_check">
</div>
<button class="btn btn-danger" id="id_member_button-__prefix__">Delete</button>
</div>
<div class="row input-group input-group-md border" id="form-div-__prefix__-joad" v-if="joad_seen">
<div class="col">To register for a JOAD session select start date:</div>
<div class="col">
<select name="member_set-__prefix__-joad" class="form-control m-2 costs" id="id_member_set-__prefix__-joad" v-model="member_joad">
<option value="" selected>None</option>
<option value="2020-05-24">2020-05-24</option>
</select>
</div>
</div>
</div>
</script>
js
var app1
var form_count = 0
var max_forms
var MemberForm
var MemberFormComponentId = 0
$(document).ready(function() {
app1 = new Vue({
el: '#app',
data: {
members: [],
},
methods: {
add_member: function() {
let v = new Vue({
el: "#form-div-" + form_count,
created: function() {
$("#app").html($("#app").html() + $("#member-form-template").html().replace(/__prefix__/g, form_count))
},
data: {
seen: false,
first_name: $("#id_member_set-" + form_count + "-first_name"),
last_name: $("#id_member_set-" + form_count + "-last_name"),
dob_id: $("#id_member_set-" + form_count + "-dob"),
joad: $("#id_member_set-" + form_count + "-joad"),
form_id: form_count,
},
methods: {
joad_check() {
if (this.member_dob === "") {
return false
}
var bd = new Date(this.member_dob);
var joad_date = new Date();
joad_date.setFullYear(joad_date.getFullYear() - 21);
this.joad_seen = (bd > joad_date && bd < new Date())
},
disable_delete () {
$("#id_member_button-"+ this.form_id).prop('disabled', true);
}
},
mounted: function () {
if(this.form_id == 0) {
$("#id_member_button-"+ this.form_id).prop('disabled', true);
}
}
})
this.members.push(v)
form_count++
MemberFormComponentId++
if( form_count > max_forms) {
$("#id_btn_add_row").prop('disabled', true);
} else {
$("#id_btn_add_row").prop('disabled', false);
}
}
}
})
app1.add_member()
})

Related

Cannot use 'in' operator to search for 'xxxxx' in undefined in vue.js

I'm trying to create a modal form that will add a record. I am able to display the default values from data but as soon as I try to modify the field, I get the following error whenever I try to type changes to the input box.
*vue.min.js:6 TypeError: Cannot use 'in' operator to search for 'fullname' in undefined
at a.ke [as $set] (vue.min.js:6)
at input (eval at Ya (vue.min.js:1), <anonymous>:3:2182)
at He (vue.min.js:6)
at HTMLInputElement.n (vue.min.js:6)
at HTMLInputElement.Yr.o._wrapper (vue.min.js:6)*
In given Below I added the code of the component I'm trying to create:
Any help, please.
var bus = new Vue();
Vue.component('leagues_add', {
props: {
show: Boolean,
is_admin: Boolean,
},
data: function () {
return {
newLeague: {"fullname":"a", "notes":"b", "group_image_path": "c"} // remember to always enclose the fieldnames in doublequotes
}
},
methods: {
closeModal() {
this.show = false;
},
showModal() {
this.show = true;
},
addLeague() {
event.preventDefault();
var formData = this.toFormData(this.newLeague);
axios.get("http://"+ window.location.hostname +"/db/leagues/index.php?action=create").then(function(response){
if (response.data.error) {
app.errorMessage = response.data.message;
} else {
app.leagues = response.data.leagues;
}
});
},
toFormData(obj) {
var fd = new FormData();
for (var i in obj) {
fd.append(i, obj[i]);
}
return fd;
},
}
,
template:
`
<!-- Add new Leage -->
<div>
<div class="text-center pb-3" >
<button type="button" class="btn btn-outline-primary bg-success text-white" #click="showModal"><b class="" style="">Add League</b></button>
</div>
<transition name="modal">
<div id="overlay" v-show="this.show">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Add League</h5>
<button type="button" class="close" #click="closeModal"><span aria-hidden="true">×</span></button>
</div>
<div class="modal-body">
<form action="#" method="POST">
<div class="form-group">
<input type="text" v-model="this.newLeague.fullname" class="form-control form-control-md" placeholder="Name of League">
</div>
<div class="form-group">
<textarea v-model="this.newLeague.notes" rows="3" cols="100%" name="notes" class="form-control form-control-md" placeholder="Describe this league">
</textarea>
</div>
<div class="form-group form-inline ">
<div class="col-12 p-0">
<input type="url" v-model="this.newLeague.group_image_path" class="col-5 pull-left form-control form-control-md" placeholder="Image URL">
<button class="col-4 btn btn-primary btn-md">Image</button>
</div>
</div>
<div class="form-group">
<button class="btn btn-info btn-block btn-md" #click="closeModal();addLeague();">Add this league</button>
</div>
</form>
</div>
</div>
</div>
</div>
</transition>
<div>
`
});
<div class="row">
<div class="col-md-12">LEAGUES SECTION</div>
</div>
<div class="row mt-2">
<div class="col-md-12">
<leagues_add :show="true" />
</div>
</div>
The problem is here:
v-model="this.newLeague.fullname"
You cannot use this. with v-model. Instead it should be:
v-model="newLeague.fullname"
You should also remove all other references to this within your template. In many cases they are harmless but sometimes, such as with v-model, they will cause problems.
Complete examples below. Note how the first input does not function correctly when editing the text.
new Vue({
el: '#app1',
data () {
return { newLeague: { fullname: 'League 1' } }
}
})
new Vue({
el: '#app2',
data () {
return { newLeague: { fullname: 'League 1' } }
}
})
<script src="https://unpkg.com/vue#2.6.11/dist/vue.js"></script>
<div id="app1">
<input type="text" v-model="this.newLeague.fullname">
{{ newLeague.fullname }}
</div>
<div id="app2">
<input type="text" v-model="newLeague.fullname">
{{ newLeague.fullname }}
</div>
I followed the way #skirtle wrote the data section and it worked.
My original syntax was:
data: function () {
return {
newLeague: {"fullname":"a", "notes":"b", "group_image_path": "c"} }
}
to
data () {
return { newLeague: { fullname: 'League 1' } }
}

Local-Storage in Vue.JS

I am working on Vue.JS and I tried to use local-storage with it to save data. In my code, I can store and retrieve all data with local-storage except line-through effect. Here, I am trying to store actual boolean value of line-through effect in local-storage and want to retrieve that value on to-do list app.
<title>To Do List</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js'></script>
<style>
.taskDone {
text-decoration: line-through;
}
</style>
</head>
<body>
<div id="todo-list" class="container">
<div class="container col-sm-8 col-sm-offset-2">
<h1 class="text-center"> <big><b> To Do List </b> </big></h1>
<h5 class="text-center"> <span v-show="itemsTodo.length"> ({{ itemsTodo.length }} pending) </span></h5>
<div class="col-md-8">
<button v-if="state === 'default'" class="btn btn-primary" #click="changeState('edit') ">Add Item</button>
<button v-else class="btn btn-info" #click="changeState('default')">Cancel</button>
</div>
<br>
<br>
<div v-if="state === 'edit'" >
<div class="col-sm-4">
<input class='form-control' v-model="newItem" type="text" placeholder="Type here" #keyup.enter="saveItem" >
</div>
<div class="col-sm-4">
<input class="form-control" v-model="newdate" type="date" type="text"/>
</div>
<div class="col-sm-4">
<button class='btn btn-primary btn-block' v-bind:disabled="newItem.length === 0"#click= "saveItem">Save Item</button>
</div>
</div>
<br>
<br>
<ul type="none" class="list-group">
<li class="list-group-item" v-for="(item,index,date) in items" :class="{taskDone : item.completed}" >
<h4>
<input type="checkbox" v-model="item.completed" #click="item.completed = !item.completed">
<button class="btn btn-primary " #click.stop="removeitems(index)">× </button>
<b><i> {{ item.label }} {{ item.date }} </i></b></h4>
</li>
</ul>
<h2 v-if="items.length === 0">Nice Job! Nothing in TO DO LIST</h2>
<div class="col-sm-4">
<button class="btn btn-warning btn-block" #click="clearcompleted"> Clear Completed</button>
</div>
<div class="col-sm-4">
<button class="btn btn-danger btn-block" #click="clearAll"> Clear All</button>
</div>
</div>
</div>
2. Vue.JS code
<script src="https://unpkg.com/vue" ></script>
<script>
var todolist = new Vue({
el: '#todo-list',
data : {
state : 'edit',
header: 'To Do List',
newItem: '',
newdate: '',
items: [
{
label:'coffee',
completed:false,
date:'2019-06-20' ,
},
{
label:'tea',
completed:false,
date:'2019-06-19' ,
},
{
label:'milk',
completed:false,
date:'2019-06-19' ,
},
]
},
computed:{
itemsDone(){
return this.items.filter(items => items.completed)
},
itemsTodo(){
return this.items.filter(items =>! items.completed)
},
},
methods:{
saveItem: function(){
if (this.newItem != ''){
this.items.push({
label:this.newItem,
completed: false,
date : this.newdate,
});
this.newItem = '';
this.newdate = '';
}},
changeState: function(newState){
this.state = newState;
this.newItem = '';
this.newdate = '';
},
removeitems(index){
this.items.splice(index,1);
},
clearcompleted (){
this.items = this.itemsTodo;
},
clearAll: function(){
this.items = [ ];
},
},
mounted(){
console.log('App Mounted!');
if (localStorage.getItem('items')) this.items = JSON.parse(localStorage.getItem('items'));
},
watch: {
items:{
handler(){
localStorage.setItem('items',JSON.stringify(this.items));
},
},
},
});
</script>
I expect correct boolean value of line-through effect to be stored in local-storage. So that, appropriate effect will show on browser.
You are just watching items. If you change something in a item (in your case completed) the handler will not be called and your change is not stored.
You could use a "deep" watcher but i suggest to call your save logic whenever you changed something.

v-model is not working on dynamic radio buttons, vue js

var vue = new Vue({
el: '#app',
data: function (){
return {
categories: null,
foodItems: null,
selectedCategoryId: null
}
},
mounted: function() {
axios
.get('#Url.Action("GetCategories", "Home", new { area="Canteen" })')
.then(response => (this.categories = response.data))
}
});
<span>selected: {{ selectedCategoryId }}</span>
<div class="box box-default">
<div class="box-header with-border">
<div class="text-center text-uppercase"><b>Select</b></div>
</div>
<div class="box-body">
<div class="btn-group-vertical btn-block btn-group-toggle" data-toggle="buttons">
<label v-for="(category, index) in categories" class="btn btn-default active">
<input :id="'option-' + category.id" :value="category.id" type="radio" name="categories" autocomplete="off" v-model="selectedCategoryId" />{{ category.name }}
</label>
</div>
</div>
</div>
Here is working example
var vue = new Vue({
el: '#app',
data: function (){
return {
categories: null,
foodItems: null,
selectedCategoryId: null,
isdata:false
}
},
mounted: function() {
console.log("mounted");
var vm = this;
setTimeout(function(){
vm.categories = [
{id:1,name:'test1'},
{id:2,name:'test2'},
{id:3,name:'test3'}
];
vm.isdata = true;
}, 2000);
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<span>selected: {{ selectedCategoryId }}</span>
<div class="box box-default">
<div class="box-header with-border">
<div v-show="!isdata">Loading....</div>
<div v-show="isdata" class="text-center text-uppercase"><b>Select</b></div>
</div>
<div class="box-body">
<div class="btn-group-vertical btn-block btn-group-toggle" data-toggle="buttons">
<label v-for="(category, index) in categories" class="btn btn-default active">
<input :id="'option-' + category.id" :value="category.id" type="radio" name="categories" autocomplete="off" v-model="selectedCategoryId" />{{ category.name }}
</label>
</div>
</div>
</div>
</div>

vuejs :disabled doesn't work

I have a problem on reactivating the button even if the conditional statement works.
it looked like the v-model wasn't communicating with the data but with a simple interpolation the value was updated.
I don't really know where I'm doing wrong on the code.
<template>
<div class="col-sm-6 col-md-4">
<div class="panel panel-success">
<div class="panel-heading">
<h3 class="panel-title">{{stock.name}}
<small>(Price: {{stock.price}})</small>
</h3>
</div>
<div class="panel-body">
<div class="pull-left">
<input v-model="quantity" type="number" class="form-control" placeholder="Quantity">
</div>
<div class="pull-right">
<button class="btn btn-success" #click="buyStock" :disabled="isDisabled">Buy</button>
</div>
<p>{{quantity}}</p>
</div>
</div>
</div>
</template>
<script>
export default {
props: [
"stock",
],
data() {
return {
quantity: 0,
}
},
methods: {
buyStock() {
const order = {
stockId: this.stock.id,
stockPrice: this.stock.price,
quantity: this.quantity
};
console.log(order);
this.$store.dispatch("buyStock", order);
this.quantity = 0;
}
},
computed: {
isDisabled() {
if (this.quantity <= 0 || !Number.isInteger(this.quantity)) {
return true;
} else {
return false;
}
}
}
}
</script>
By default, the v-model directive binds the value as a String. So both checks in your isDisabled computed will always fail.
If you want to bind quantity as a number, you can add the .number modifier like so:
<input v-model.number="quantity" type="number" ... >
Here's a working example:
new Vue({
el: '#app',
data() {
return { quantity: 0 }
},
computed: {
isDisabled() {
return (this.quantity <= 0 || !Number.isInteger(this.quantity))
}
}
})
<template>
<div class="col-sm-6 col-md-4">
<div class="panel panel-success">
<div class="panel-heading">
<h3 class="panel-title">{{stock.name}}
<small>(Price: {{stock.price}})</small>
</h3>
</div>
<div class="panel-body">
<div class="pull-left">
<input v-model="quantity" type="number" class="form-control" placeholder="Quantity">
</div>
<div class="pull-right">
<button class="btn btn-success" #click="buyStock" :disabled="isDisabled">Buy</button>
</div>
<p>{{quantity}}</p>
</div>
</div>
</div>
</template>
<script>
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.min.js"></script>
<div id="app">
<input v-model.number="quantity" type="number">
<button :disabled="isDisabled">Foo</button>
</div>

Posting response data from component to root in vue js

My modal box is inside of a vue component. When the data is submitted, I want the component to send back the response data to the parent so I can append it to the root element.
The component
<template>
<div v-if="value.startsWith('new')">
<!-- Create Client Modal -->
<div class="modal show" id="modal-create-client" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button " class="close" data-dismiss="modal" aria-hidden="true" #click.prevent="close">×</button>
<h4 class="modal-title">Details</h4>
</div>
<div class="modal-body">
<!-- Form Errors -->
<div class="alert alert-danger" v-if="createForm.errors.length > 0">
<p><strong>Whoops!</strong> Something went wrong!</p>
<br>
<ul>
<li v-for="error in createForm.errors">
{{ error }}
</li>
</ul>
</div>
<!-- Create Client Form -->
<form class="form-horizontal" role="form">
<!-- Name -->
<div class="form-group">
<label class="col-md-3 control-label">First Name</label>
<div class="col-md-7">
<input id="create-client-name" type="text" class="form-control" #keyup.enter="store" v-model="createForm.first">
</div>
</div>
<div class="form-group">
<label class="col-md-3 control-label">Last Name</label>
<div class="col-md-7">
<input type="text" class="form-control" name="last" #keyup.enter="store" v-model="createForm.last">
</div>
</div>
<div class="form-group">
<label class="col-md-3 control-label">Email</label>
<div class="col-md-7">
<input type="text" class="form-control" name="organization" #keyup.enter="store" v-model="createForm.email">
<span class="help-block">Email is required for invoices</span>
</div>
</div>
<div class="form-group">
<label class="col-md-3 control-label">Organization</label>
<div class="col-md-7">
<input type="text" class="form-control" name="organization" #keyup.enter="store" v-model="createForm.organization">
</div>
</div>
</form>
</div>
<!-- Modal Actions -->
<div class="modal-footer" v-if="value == 'newClient'">
<button type="button" class="btn btn-default" data-dismiss="modal" #click.prevent="close">Close</button>
<button type="button" class="btn btn-primary" #click="storeClient">Create</button>
</div>
<div class="modal-footer" v-else-if="value == 'newLead'">
<button type="button" class="btn btn-default" data-dismiss="modal" #click.prevent="close">Close</button>
<button type="button" class="btn btn-primary" #click="storeLead">Create</button>
</div>
<div class="modal-footer" v-else-if="value == 'newContact'">
<button type="button" class="btn btn-default" data-dismiss="modal" #click.prevent="close">Close</button>
<button type="button" class="btn btn-primary" #click="storeContact">Create</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
createForm: {
errors: [],
first: '',
last: '',
email: '',
organization: ''
},
};
},
props: ['value'],
/**
* Prepare the component (Vue 1.x).
*/
ready() {
this.prepareComponent();
},
/**
* Prepare the component (Vue 2.x).
*/
mounted() {
var vm = this
this.prepareComponent();
},
methods: {
/**
* Prepare the component.
*/
prepareComponent() {
$('#modal-create-client').on('shown.bs.modal', () => {
$('#create-client-name').focus();
});
$("#modal-create-client").on("hide.bs.modal", function(e) {
$(this).removeData('bs.modal');
});
},
close() {
$('#modal-create-client').removeClass('show');
},
/**
* Create a new client for the user.
**/
storeClient() {
this.persistClient(
'post', './clients/add',
this.createForm, '#modal-create-client'
);
},
storeLead() {
this.persistClient(
'post', './leads/add',
this.createForm, '#modal-create-client'
);
},
storeContact() {
this.persistClient(
'post', './contacts/add',
this.createForm, '#modal-create-client'
);
},
/**
* Persist the client to storage using the given form.
*/
persistClient(method, uri, form, modal) {
form.errors = [];
this.$http[method](uri, form).then(response => {
location.reload();
$(modal).modal('hide');
}).catch(response => {
if (typeof response.data === 'object') {
form.errors = _.flatten(_.toArray(response.data));
} else {
form.errors = ['Something went wrong. Please try again.'];
}
});
},
watch: {
value: function (value) {
// update value
$(this.$el).val(value)
},
}
}
}
</script>
The root Element
var MyComponent = Vue.component('my-ajax-component',
require('./components/Toolbar.vue') );
new Vue({
el: '#select',
data: {
selected: ''
},
components: {
// <my-component> will only be available in parent's template
'my-ajax-component': MyComponent
}
});
and my view
<div class="form-group clearfix">
<div class="col-xs-12" id="select">
{!! Form::label('client_id', 'Choose Client:', ['class' => 'control-label']) !!}
{!! Form::select('client_id', ['newClient' => 'New Client', $clients], null, ['title' => 'Select Client', 'class' => 'form-control selectpicker', 'v-model' => 'selected', 'data-live-search' => 'true']) !!}
<br>
<my-ajax-component v-bind:value="selected"></my-ajax-component>
</div>
</div>
Instead of location reload I want to append the response data to the select element which is my roo
I changed my root element to
new Vue({
el: '#select',
data: {
selected: '',
data: ''
},
components: {
// <my-component> will only be available in parent's template
'my-ajax-component': MyComponent
},
methods: {
handler: function(data) {
console.log('this is my data' + data)
}
}
my component now has
this.$emit('data-received',response)
and put v-on in the child component
<my-ajax-component v-bind:value="selected" v-on:data-received='handler(data)'></my-ajax-component>
I get data undefined or nothing
I can see the data returned in the post .. it's the id of my object ...should I json encode it
The easiest way can be to emit the event with data in child component when you get the response.
In child:
this.$emit('data-received',response)
In parrent:
<child-component v-on:data-received='handler(data)'>
In handler function in parrent, do whatever you want with data.
UPDATED:
Your backend should return JSON to follow REST Api standards.
Every endpoint of API should return JSON, even if it is simple string.
Instead of
In parrent:
<child-component v-on:data-received='handler(data)'>
I used In parrent:
<child-component v-on:data-received='handler'>