how do I put a button to select the data of a registration and update this code in Vue.js? (CLOSED) - vue.js

I made a "table" where I register this data, it is a crud that saves in memory and I already made the option to save and delete, I now need to update/edit, I would like to make a button that selects the registration and the data of it appears for editing, basically it's a simple crud system in Vue.js. I've done it to save and to delete, now I need the rest.
<body>
<div id="app">
<div class="container">
<h1>Formulário de Cadastro v.Vue</h1>
<form class="row">
<div class="col-sm-2">
<label>Nome:</label>
<input type="text" placeholder="Nome" v-model="form.name" class="form-control">
</div>
<div class="col-sm-2">
<label>Idade:</label>
<input type="number" placeholder="Idade" v-model="form.age" class="form-control">
</div>
<div class="col-sm-2">
<label>Telefone:</label>
<input type="tel" placeholder="Telefone" v-model="form.phone" class="form-control">
</div>
<div class="col-sm-2">
<label>Gênero:</label>
<select class="form-select form-control" v-model="form.gender">
<option>Masculino</option>
<option>Feminino</option>
</select>
</div>
<div class="col-sm-2">
<label>Data de Cadastro:</label>
<input type="date" v-model="form.registerdate" class="form-control">
</div>
</form>
<br />
<div>
<input type="button" value="Salvar" v-on:click="save" class="btn btn-sm btn-success" />
<input type="button" value="Atualizar" v-on:click="update" class="btn btn-sm btn-primary" />
</div>
<br />
<div class="container-fluid">
<table class="table table-sm table-bordered" v-if="contacts.length > 0">
<tr>
<th>ID</th>
<th>Nome</th>
<th>Idade</th>
<th>Telefone</th>
<th>Gênero</th>
<th>Data de Cadastro</th>
<th>Opções</th>
</tr>
<tr v-for="contact in contacts">
<td>{{contact.id}}</td>
<td>{{contact.name}}</td>
<td>{{contact.age}}</td>
<td>{{contact.phone}}</td>
<td>{{contact.gender}}</td>
<td>{{contact.registerdate}}</td>
<td>
<input type="button" value="Excluir" class="btn btn-sm btn-danger"
v-on:click="remove(contact.id)" />
<input type="button" value="Selecionar" class="btn btn-sm btn-primary"
v-on:click="select(contact.id)" />
</td>
</tr>
<div :class="classAlert" role="alert" v-if="showMessage">
{{messageAlert}}
</div>
</table>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#2/dist/vue.js"></script>
<script>
var app = new Vue({
el: "#app",
data: {
classAlert: '',
messageAlert: '',
showMessage: false,
inputName: '',
currentCount: 0,
currentId: 1,
form: {
name: '',
age: 0,
phone: '',
gender: '',
registerdate: ''
},
contacts: []
},
methods: {
incrementCount: function () {
this.currentCount++;
},
save: function () {
var _this = this;
// if (this.selectContact === null) {
// }
// else {
// }
this.contacts.push({
id: this.currentId++,
name: this.form.name,
age: this.form.age,
phone: this.form.phone,
gender: this.form.gender,
registerdate: this.form.registerdate
});
this.classAlert = 'alert alert-success';
this.messageAlert = "Salvo Com sucesso";
this.showMessage = true;
setTimeout(function () {
_this.showMessage = false
}, 3000);
},
remove: function (id) {
var _this = this;
this.contacts = this.contacts.filter(p => p.id != id);
this.classAlert = 'alert alert-danger'
this.messageAlert = "Excluido Com sucesso";
this.showMessage = true;
setTimeout(function () {
_this.showMessage = false
}, 3000);
},
select: function (id) {
var _this = this;
const contact = this.contacts.filter(contact => contact.id === id);
this.form.name = contact[0].name;
this.form.age = contact[0].age;
this.form.phone = contact[0].phone;
this.form.gender = contact[0].gender;
this.form.registerdate = contact[0].registerdate;
},
update: function () {
var _this = this;
const l_contactUpdate = this.contacts.filter(l_contactUpdate => l_contactUpdate.name === name);
this.form.name = form.name;
}
// update/edit method??
}
});
</script>

Related

The below given code is not working for me. And giving an error "vueuploadComponent.html:396 Uncaught ReferenceError: FileUpload is not defined"

Here is the code in vue.js for multi Image uploading. Actually this code is not working for me I am facing an error "vueuploadComponent.html:396 Uncaught ReferenceError: FileUpload is not defined". So here I thing i doing some thing wrong with import a module. So please let me know where I am going wrong.
<template>
<div id = "vueUpload" class="example-full">
<button type="button" class="btn btn-danger float-right btn-is-option" #click.prevent="isOption = !isOption">
<i class="fa fa-cog" aria-hidden="true"></i>
Options
</button>
<h1 id="example-title" class="example-title">Full Example</h1>
<div v-show="$refs.upload && $refs.upload.dropActive" class="drop-active">
<h3>Drop files to upload</h3>
</div>
<div class="upload" v-show="!isOption">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>#</th>
<th>Thumb</th>
<th>Name</th>
<th>Width</th>
<th>Height</th>
<th>Size</th>
<th>Speed</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr v-if="!files.length">
<td colspan="9">
<div class="text-center p-5">
<h4>Drop files anywhere to upload<br/>or</h4>
<label :for="name" class="btn btn-lg btn-primary">Select Files</label>
</div>
</td>
</tr>
<tr v-for="(file, index) in files" :key="file.id">
<td>{{index}}</td>
<td>
<img class="td-image-thumb" v-if="file.thumb" :src="file.thumb" />
<span v-else>No Image</span>
</td>
<td>
<div class="filename">
{{file.name}}
</div>
<div class="progress" v-if="file.active || file.progress !== '0.00'">
<div :class="{'progress-bar': true, 'progress-bar-striped': true, 'bg-danger': file.error, 'progress-bar-animated': file.active}" role="progressbar" :style="{width: file.progress + '%'}">{{file.progress}}%</div>
</div>
</td>
<td>{{file.width || 0}}</td>
<td>{{file.height || 0}}</td>
<td>{{$formatSize(file.size)}}</td>
<td>{{$formatSize(file.speed)}}</td>
<td v-if="file.error">{{file.error}}</td>
<td v-else-if="file.success">success</td>
<td v-else-if="file.active">active</td>
<td v-else></td>
<td>
<div class="btn-group">
<button class="btn btn-secondary btn-sm dropdown-toggle" type="button">
Action
</button>
<div class="dropdown-menu">
<a :class="{'dropdown-item': true, disabled: file.active || file.success || file.error === 'compressing' || file.error === 'image parsing'}" href="#" #click.prevent="file.active || file.success || file.error === 'compressing' ? false : onEditFileShow(file)">Edit</a>
<a :class="{'dropdown-item': true, disabled: !file.active}" href="#" #click.prevent="file.active ? $refs.upload.update(file, {error: 'cancel'}) : false">Cancel</a>
<a class="dropdown-item" href="#" v-if="file.active" #click.prevent="$refs.upload.update(file, {active: false})">Abort</a>
<a class="dropdown-item" href="#" v-else-if="file.error && file.error !== 'compressing' && file.error !== 'image parsing' && $refs.upload.features.html5" #click.prevent="$refs.upload.update(file, {active: true, error: '', progress: '0.00'})">Retry upload</a>
<a :class="{'dropdown-item': true, disabled: file.success || file.error === 'compressing' || file.error === 'image parsing'}" href="#" v-else #click.prevent="file.success || file.error === 'compressing' || file.error === 'image parsing' ? false : $refs.upload.update(file, {active: true})">Upload</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#" #click.prevent="$refs.upload.remove(file)">Remove</a>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="example-foorer">
<div class="footer-status float-right">
Drop: {{$refs.upload ? $refs.upload.drop : false}},
Active: {{$refs.upload ? $refs.upload.active : false}},
Uploaded: {{$refs.upload ? $refs.upload.uploaded : true}},
Drop active: {{$refs.upload ? $refs.upload.dropActive : false}}
</div>
<div class="btn-group">
<file-upload
class="btn btn-primary dropdown-toggle"
:post-action="postAction"
:put-action="putAction"
:extensions="extensions"
:accept="accept"
:multiple="multiple"
:directory="directory"
:create-directory="createDirectory"
:size="size || 0"
:thread="thread < 1 ? 1 : (thread > 5 ? 5 : thread)"
:headers="headers"
:data="data"
:drop="drop"
:drop-directory="dropDirectory"
:add-index="addIndex"
v-model="files"
#input-filter="inputFilter"
#input-file="inputFile"
ref="upload">
<i class="fa fa-plus"></i>
Select
</file-upload>
<div class="dropdown-menu">
<label class="dropdown-item" :for="name">Add files</label>
<a class="dropdown-item" href="#" #click="onAddFolder">Add folder</a>
<a class="dropdown-item" href="#" #click.prevent="addData.show = true">Add data</a>
</div>
</div>
<button type="button" class="btn btn-success" v-if="!$refs.upload || !$refs.upload.active" #click.prevent="$refs.upload.active = true">
<i class="fa fa-arrow-up" aria-hidden="true"></i>
Start Upload
</button>
<button type="button" class="btn btn-danger" v-else #click.prevent="$refs.upload.active = false">
<i class="fa fa-stop" aria-hidden="true"></i>
Stop Upload
</button>
</div>
</div>
<div class="option" v-show="isOption">
<div class="form-group">
<label for="accept">Accept:</label>
<input type="text" id="accept" class="form-control" v-model="accept">
<small class="form-text text-muted">Allow upload mime type</small>
</div>
<div class="form-group">
<label for="extensions">Extensions:</label>
<input type="text" id="extensions" class="form-control" v-model="extensions">
<small class="form-text text-muted">Allow upload file extension</small>
</div>
<div class="form-group">
<label>PUT Upload:</label>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="put-action" id="put-action" value="" v-model="putAction"> Off
</label>
</div>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="put-action" id="put-action" value="/upload/put" v-model="putAction"> On
</label>
</div>
<small class="form-text text-muted">After the shutdown, use the POST method to upload</small>
</div>
<div class="form-group">
<label for="thread">Thread:</label>
<input type="number" max="5" min="1" id="thread" class="form-control" v-model.number="thread">
<small class="form-text text-muted">Also upload the number of files at the same time (number of threads)</small>
</div>
<div class="form-group">
<label for="size">Max size:</label>
<input type="number" min="0" id="size" class="form-control" v-model.number="size">
</div>
<div class="form-group">
<label for="minSize">Min size:</label>
<input type="number" min="0" id="minSize" class="form-control" v-model.number="minSize">
</div>
<div class="form-group">
<label for="autoCompress">Automatically compress:</label>
<input type="number" min="0" id="autoCompress" class="form-control" v-model.number="autoCompress">
<small class="form-text text-muted" v-if="autoCompress > 0">More than {{$formatSize(autoCompress)}} files are automatically compressed</small>
<small class="form-text text-muted" v-else>Set up automatic compression</small>
</div>
<div class="form-group">
<div class="form-check">
<label class="form-check-label">
<input type="checkbox" id="add-index" class="form-check-input" v-model="addIndex"> Start position to add
</label>
</div>
<small class="form-text text-muted">Add a file list to start the location to add</small>
</div>
<div class="form-group">
<div class="form-check">
<label class="form-check-label">
<input type="checkbox" id="drop" class="form-check-input" v-model="drop"> Drop
</label>
</div>
<small class="form-text text-muted">Drag and drop upload</small>
</div>
<div class="form-group">
<div class="form-check">
<label class="form-check-label">
<input type="checkbox" id="drop-directory" class="form-check-input" v-model="dropDirectory"> Drop directory
</label>
</div>
<small class="form-text text-muted">Not checked, filter the dragged folder</small>
</div>
<div class="form-group">
<div class="form-check">
<label class="form-check-label">
<input type="checkbox" id="create-directory" class="form-check-input" v-model="createDirectory"> Create Directory
</label>
</div>
<small class="form-text text-muted">The directory file will send an upload request. The mime type is <code>text/directory</code></small>
</div>
<div class="form-group">
<div class="form-check">
<label class="form-check-label">
<input type="checkbox" id="upload-auto" class="form-check-input" v-model="uploadAuto"> Auto start
</label>
</div>
<small class="form-text text-muted">Automatically activate upload</small>
</div>
<div class="form-group">
<button type="button" class="btn btn-primary btn-lg btn-block" #click.prevent="isOption = !isOption">Confirm</button>
</div>
</div>
<div :class="{'modal-backdrop': true, 'fade': true, show: addData.show}"></div>
<div :class="{modal: true, fade: true, show: addData.show}" id="modal-add-data" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Add data</h5>
<button type="button" class="close" #click.prevent="addData.show = false">
<span>×</span>
</button>
</div>
<form #submit.prevent="onAddData">
<div class="modal-body">
<div class="form-group">
<label for="data-name">Name:</label>
<input type="text" class="form-control" required id="data-name" placeholder="Please enter a file name" v-model="addData.name">
<small class="form-text text-muted">Such as <code>filename.txt</code></small>
</div>
<div class="form-group">
<label for="data-type">Type:</label>
<input type="text" class="form-control" required id="data-type" placeholder="Please enter the MIME type" v-model="addData.type">
<small class="form-text text-muted">Such as <code>text/plain</code></small>
</div>
<div class="form-group">
<label for="content">Content:</label>
<textarea class="form-control" required id="content" rows="3" placeholder="Please enter the file contents" v-model="addData.content"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" #click.prevent="addData.show = false">Close</button>
<button type="submit" class="btn btn-primary">Save</button>
</div>
</form>
</div>
</div>
</div>
<div :class="{'modal-backdrop': true, 'fade': true, show: editFile.show}"></div>
<div :class="{modal: true, fade: true, show: editFile.show}" id="modal-edit-file" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Edit file</h5>
<button type="button" class="close" #click.prevent="editFile.show = false">
<span>×</span>
</button>
</div>
<form #submit.prevent="onEditorFile">
<div class="modal-body">
<div class="form-group">
<label for="name">Name:</label>
<input type="text" class="form-control" required id="name" placeholder="Please enter a file name" v-model="editFile.name">
</div>
<div class="form-group" v-if="editFile.show && editFile.blob && editFile.type && editFile.type.substr(0, 6) === 'image/'">
<label>Image: </label>
<div class="edit-image">
<img :src="editFile.blob" ref="editImage" />
</div>
<div class="edit-image-tool">
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary" #click="editFile.cropper.rotate(-90)" title="cropper.rotate(-90)"><i class="fa fa-undo" aria-hidden="true"></i></button>
<button type="button" class="btn btn-primary" #click="editFile.cropper.rotate(90)" title="cropper.rotate(90)"><i class="fa fa-repeat" aria-hidden="true"></i></button>
</div>
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary" #click="editFile.cropper.crop()" title="cropper.crop()"><i class="fa fa-check" aria-hidden="true"></i></button>
<button type="button" class="btn btn-primary" #click="editFile.cropper.clear()" title="cropper.clear()"><i class="fa fa-remove" aria-hidden="true"></i></button>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" #click.prevent="editFile.show = false">Close</button>
<button type="submit" class="btn btn-primary">Save</button>
</div>
</form>
</div>
</div>
</div>
</div>
</template>
<script src='https://unpkg.com/vue#2.5.16/dist/vue.js'></script>
<script type="module">
import Cropper from 'cropperjs'
import ImageCompressor from '#xkeshi/image-compressor'
import { FileUpload } from "vue-upload-component";
</script>
<script>
new Vue({
el: '#vueUpload',
components: {
FileUpload,
},
data() {
return {
files: [],
accept: 'image/png,image/gif,image/jpeg,image/webp',
extensions: 'gif,jpg,jpeg,png,webp',
// extensions: ['gif', 'jpg', 'jpeg','png', 'webp'],
// extensions: /\.(gif|jpe?g|png|webp)$/i,
minSize: 1024,
size: 1024 * 1024 * 10,
multiple: true,
directory: false,
drop: true,
dropDirectory: true,
createDirectory: false,
addIndex: false,
thread: 3,
name: 'file',
postAction: '/upload/post',
putAction: '/upload/put',
headers: {
'X-Csrf-Token': 'xxxx',
},
data: {
'_csrf_token': 'xxxxxx',
},
autoCompress: 1024 * 1024,
uploadAuto: false,
isOption: false,
addData: {
show: false,
name: '',
type: '',
content: '',
},
editFile: {
show: false,
name: '',
}
}
},
watch: {
'editFile.show'(newValue, oldValue) {
// 关闭了 自动删除 error
if (!newValue && oldValue) {
this.$refs.upload.update(this.editFile.id, { error: this.editFile.error || '' })
}
if (newValue) {
this.$nextTick( () => {
if (!this.$refs.editImage) {
return
}
let cropper = new Cropper(this.$refs.editImage, {
autoCrop: false,
})
this.editFile = {
...this.editFile,
cropper
}
})
}
},
'addData.show'(show) {
if (show) {
this.addData.name = ''
this.addData.type = ''
this.addData.content = ''
}
},
},
methods: {
inputFilter(newFile, oldFile, prevent) {
if (newFile && !oldFile) {
// Before adding a file
// 添加文件前
// Filter system files or hide files
// 过滤系统文件 和隐藏文件
if (/(\/|^)(Thumbs\.db|desktop\.ini|\..+)$/.test(newFile.name)) {
return prevent()
}
// Filter php html js file
// 过滤 php html js 文件
if (/\.(php5?|html?|jsx?)$/i.test(newFile.name)) {
return prevent()
}
// Automatic compression
// 自动压缩
if (newFile.file && newFile.error === "" && newFile.type.substr(0, 6) === 'image/' && this.autoCompress > 0 && this.autoCompress < newFile.size) {
newFile.error = 'compressing'
const imageCompressor = new ImageCompressor(null, {
convertSize: 1024 * 1024,
maxWidth: 512,
maxHeight: 512,
})
imageCompressor.compress(newFile.file)
.then((file) => {
this.$refs.upload.update(newFile, { error: '', file, size: file.size, type: file.type })
})
.catch((err) => {
this.$refs.upload.update(newFile, { error: err.message || 'compress' })
})
}
}
if (newFile && newFile.error === "" && newFile.file && (!oldFile || newFile.file !== oldFile.file)) {
// Create a blob field
// 创建 blob 字段
newFile.blob = ''
let URL = (window.URL || window.webkitURL)
if (URL) {
newFile.blob = URL.createObjectURL(newFile.file)
}
// Thumbnails
// 缩略图
newFile.thumb = ''
if (newFile.blob && newFile.type.substr(0, 6) === 'image/') {
newFile.thumb = newFile.blob
}
}
// image size
// image 尺寸
if (newFile && newFile.error === '' && newFile.type.substr(0, 6) === "image/" && newFile.blob && (!oldFile || newFile.blob !== oldFile.blob)) {
newFile.error = 'image parsing'
let img = new Image();
img.onload = () => {
this.$refs.upload.update(newFile, {error: '', height: img.height, width: img.width})
}
img.οnerrοr = (e) => {
this.$refs.upload.update(newFile, { error: 'parsing image size'})
}
img.src = newFile.blob
}
},
// add, update, remove File Event
inputFile(newFile, oldFile) {
if (newFile && oldFile) {
// update
if (newFile.active && !oldFile.active) {
// beforeSend
// min size
if (newFile.size >= 0 && this.minSize > 0 && newFile.size < this.minSize) {
this.$refs.upload.update(newFile, { error: 'size' })
}
}
if (newFile.progress !== oldFile.progress) {
// progress
}
if (newFile.error && !oldFile.error) {
// error
}
if (newFile.success && !oldFile.success) {
// success
}
}
if (!newFile && oldFile) {
// remove
if (oldFile.success && oldFile.response.id) {
// $.ajax({
// type: 'DELETE',
// url: '/upload/delete?id=' + oldFile.response.id,
// })
}
}
// Automatically activate upload
if (Boolean(newFile) !== Boolean(oldFile) || oldFile.error !== newFile.error) {
if (this.uploadAuto && !this.$refs.upload.active) {
this.$refs.upload.active = true
}
}
},
alert(message) {
alert(message)
},
onEditFileShow(file) {
this.editFile = { ...file, show: true }
this.$refs.upload.update(file, { error: 'edit' })
},
onEditorFile() {
if (!this.$refs.upload.features.html5) {
this.alert('Your browser does not support')
this.editFile.show = false
return
}
let data = {
name: this.editFile.name,
error: '',
}
if (this.editFile.cropper) {
let binStr = atob(this.editFile.cropper.getCroppedCanvas().toDataURL(this.editFile.type).split(',')[1])
let arr = new Uint8Array(binStr.length)
for (let i = 0; i < binStr.length; i++) {
arr[i] = binStr.charCodeAt(i)
}
data.file = new File([arr], data.name, { type: this.editFile.type })
data.size = data.file.size
}
this.$refs.upload.update(this.editFile.id, data)
this.editFile.error = ''
this.editFile.show = false
},
// add folder
onAddFolder() {
if (!this.$refs.upload.features.directory) {
this.alert('Your browser does not support')
return
}
let input = document.createElement('input')
input.style = "background: rgba(255, 255, 255, 0);overflow: hidden;position: fixed;width: 1px;height: 1px;z-index: -1;opacity: 0;"
input.type = 'file'
input.setAttribute('allowdirs', true)
input.setAttribute('directory', true)
input.setAttribute('webkitdirectory', true)
input.multiple = true
document.querySelector("body").appendChild(input)
input.click()
input.onchange = (e) => {
this.$refs.upload.addInputFile(input).then(function() {
document.querySelector("body").removeChild(input)
})
}
},
onAddData() {
this.addData.show = false
if (!this.$refs.upload.features.html5) {
this.alert('Your browser does not support')
return
}
let file = new window.File([this.addData.content], this.addData.name, {
type: this.addData.type,
})
this.$refs.upload.add(file)
}
}
})
</script>
Assuming you're using Lian Yue's vue-upload-component and have installed it successfully, try removing the curly braces from your import:
import FileUpload from "vue-upload-component";
This will set FileUpload to the default export from vue-upload-component which I believe is what you want

I do not commit and the state is updated in Vuex

I have a bit frustrating problem, result that I have a select / option where I choose an item and then a modal is opened and just by clicking on Add / update to table, it just commits to the state "itemstabla", but result that when I give it to edit item in the table and the model is opened and I change, for example, quantity to another digit, the state "itemstabla" is updated without executing the commit, it should only be done when clicking on the modal button.
My store:
let store = {
state: {
token: localStorage.getItem('access_token') || null,
items: [],
itemstabla: [],
monedas: [],
impuestos: [],
venta: [],
estadotienda: '',
},
getters: {
loggedIn(state) {
return state.token !== null
},
},
mutations: {
EliminarItemTabla(state, item) {
var index = state.itemstabla.findIndex(c => c.id == item);
state.itemstabla.splice(index, 1);
},
retrieveToken(state, token) {
state.token = token
},
setmonedas(state, monedas) {
state.monedas = monedas
},
setventa(state, venta) {
state.venta = venta
},
setitemstabla(state, items) {
let found = state.itemstabla.find(item => item.id == items.id);
if(found) {
if(items.accion=='agregar') {
found.cantidad = parseInt(found.cantidad) + parseInt(items.cantidad)
}
else {
found.cantidad = parseInt(items.cantidad)
}
}
else {
state.itemstabla.push(items)
}
},
actualizaritemstabla(state) {
state.itemstabla.forEach(function (item) {
if ((state.venta.moneda_id == 'S/' && item.moneda == 'S/') || (state.venta.moneda_id == '$' && item.moneda == '$')) {
item.precio = parseFloat(item.precio).toFixed(2)
}
else if(state.venta.moneda_id == '$' && item.moneda == 'S/') {
item.moneda = '$'
item.precio = parseFloat(parseFloat(item.precio) / parseFloat(state.venta.tipocambio)).toFixed(2)
}
else if(state.venta.moneda_id == 'S/' && item.moneda == '$') {
item.moneda = 'S/'
item.precio = parseFloat(parseFloat(item.precio) * parseFloat(state.venta.tipocambio)).toFixed(2)
}
});
},
setimpuestos(state, impuestos) {
state.impuestos = impuestos
},
setmonedaid(state, moneda_id) {
state.venta.moneda_id = moneda_id
},
settipocambio(state, tipocambio) {
state.venta.tipocambio = tipocambio
},
destroyToken(state) {
state.token = null
}
},
actions: {
retrieveToken(context, credentials) {
return new Promise((resolve, reject) => {
axios.post('/api/login', {
username: credentials.username,
password: credentials.password,
})
.then(response => {
const token = response.data.access_token
localStorage.setItem('access_token', token)
context.commit('retrieveToken', token)
resolve(response)
})
.catch(error => {
reject(error)
})
})
},
destroyToken(context) {
if (context.getters.loggedIn){
return new Promise((resolve, reject) => {
axios.post('/api/logout', '', {
headers: { Authorization: "Bearer " + context.state.token }
})
.then(response => {
localStorage.removeItem('access_token')
context.commit('destroyToken')
resolve(response)
})
.catch(error => {
localStorage.removeItem('access_token')
context.commit('destroyToken')
reject(error)
})
})
}
}
}
}
export default store
My file Vue:
<template>
<div>
<!-- MODAL PARA EDITAR ITEM -->
<div class="modal fade" id="editarItem" tabindex="-1" role="dialog" aria-labelledby="editarItem" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Editar ítem</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div class="form-group row">
<label for="cantidadEdit" class="col-sm-4 col-form-label col-form-label-sm font-weight-bold">Cantidad</label>
<div class="col-sm-8">
<div class="input-group">
<input v-model="item.cantidad" type="number" class="form-control text-center font-weight-bold h2" min="1" tabindex="1" onfocus="this.select();">
</div>
</div>
</div>
<div class="form-group row">
<label for="itemEdit" class="col-sm-4 col-form-label col-form-label-sm">Stock</label>
<div class="col-sm-8">
<input type="text" class="form-control text-center font-weight-bold h2" v-model="item.stock" disabled>
</div>
</div>
<div class="form-group row">
<label for="precioEdit" class="col-sm-4 col-form-label col-form-label-sm">Precio unitario<small class="text-muted" id="porcentajefinalprecio">+ {{item.primer_margen}}%</small></label>
<div class="col-sm-8">
<input v-model="item.precio" type="text" class="form-control font-weight-bold" tabindex="2" onfocus="this.select();">
</div>
</div>
<div class="form-group row">
<label for="" class="col-sm-4 col-form-label col-form-label-sm">Tipo de impuesto</label>
<div class="col-sm-8">
<select v-model="item.impuesto" class="imp custom-select" id="imp">
<option v-for="imp in impuestos" v-bind:key="imp.id" v-bind:value="imp.id">{{ imp.nombre }}</option>
</select>
</div>
</div>
<div class="form-group row">
<label for="precioTotalEdit" class="col-sm-4 col-form-label col-form-label-sm">Subtotal <small id="simboloparatodos" class="text-muted">{{item.moneda_id}}</small></label>
<div class="col-sm-8">
<input type="text" v-model="subtotal" class="form-control form-control-sm" id="precioTotalEdit" onfocus="this.select();" readonly>
</div>
</div>
<div class="form-group row">
<label for="precioigvEdit" class="col-sm-4 col-form-label col-form-label-sm">I.G.V. </label>
<div class="col-sm-8">
<input type="text" v-model="igv" class="form-control form-control-sm" id="precioigvEdit" readonly>
</div>
</div>
<div class="form-group row">
<label for="descuentoEdit" class="col-sm-4 col-form-label col-form-label-sm">Descuento</label>
<div class="col-sm-8">
<input v-model="item.descuento" type="text" class="form-control form-control-sm" id="descuentoEdit" data-toggle="popover" data-placement="top" data-html="true" data-content="Para montos. Ej: 10<br>Para porcentajes agrega %. Ej: 10%" data-trigger="hover" tabindex="4" onfocus="this.select();">
</div>
</div>
<div class="form-group row">
<label for="precioSubtotalEdit" class="col-sm-4 col-form-label col-form-label-sm">Total <small id="simboloparatodos" class="text-muted">{{ moneda_id }}</small></label>
<div class="col-sm-8">
<input v-model="total" type="text" class="form-control font-weight-bold" id="precioSubtotalEdit" value="0" tabindex="5" onfocus="this.select();">
</div>
</div>
<div class="form-group row">
<label for="precio20Edit" class="col-sm-4 col-form-label col-form-label-sm">Precio <small class="text-muted" id="porcentajeinicial">+ {{item.primer_margen}}%</small></label>
<div class="col-sm-8">
<div id="margeninicialx">{{ item.masprimermargen }}</div>
</div>
</div>
<div class="form-group row">
<label for="precio35Edit" class="col-sm-4 col-form-label col-form-label-sm">Precio <small class="text-muted" id="porcentajefinal">+ {{item.segundo_margen}}%</small></label>
<div class="col-sm-8">
<div id="margenfinalx"> {{ item.massegundomargen }}</div>
</div>
</div>
<div class="form-group row">
<label for="itemEdit" class="col-sm-4 col-form-label col-form-label-sm">Item</label>
<div class="col-sm-8">
<small id="marcaEdit">{{ item.marca }}</small> {{ item.nombre }}<br><span class="text-muted small" title="códigos" id="codigoedit" v-for="cod in codigos" v-bind:key="cod.id">{{ cod.nombre_codigo }}-{{ cod.pivot.nombre }}<br></span>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">Cancelar</button>
<button type="button" class="btn btn-sm btn-primary" tabindex="5" #click="anadiritem">Añadir</button>
</div>
</div>
</div>
</div>
<!-- FIN DE MODAL PARA EDITAR ITEM -->
<!-- SECCION SELECCIONAR ITEM Y AGREGAR A LA TABLA -->
<div class="">
<div class="input-group input-group-sm mb-3">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<div class="prefetch">
<input type="text" id="item" class="form-control typeahead" placeholder="Buscar item (Ctrl+.)" v-model.trim="q" #keyup.enter="buscaritem">
</div>
<div class="input-group-append">
<button class="btn btn-outline-secondary" id="button-addon2" disabled></button>
</div>
</div>
</div>
<table class="table table-striped table-hover table-sm table-responsive-md text-nowrap mt-3" id="detailFactura">
<thead>
<tr>
<th>Ítem</th>
<th class="text-right">Cnt.</th>
<th class="text-right">Costo U.</th>
<th></th>
<th class="text-right text-nowrap">
SubTotal
</th>
<th class="text-right text-nowrap">
I.G.V
</th>
<th class="text-right text-nowrap"><span class="text-muted">
Total
<div id="monedaText"></div>
</span></th>
</tr>
</thead>
<tbody>
<tr v-for="itemtabla in itemstabla" v-bind:key="itemtabla.id">
<td class="overflow-hidden" style="max-width: 299px; text-overflow: ellipsis">{{ item.nombre }} <small class="text-muted">({{ itemtabla.marca }})</small> </td>
<td class="text-right"><small class="text-muted mr-1" title="Unidades">{{ itemtabla.unidad }}</small>{{ itemtabla.cantidad }}</td>
<td class="text-right">{{ itemtabla.precio }}</td>
<td class="text-right">
<i class="fas fa-pencil-alt mr-2"></i>
<i class="far fa-trash-alt"></i>
</td>
<td class="text-right">{{ preciosegunmoneda(itemtabla.precio, itemtabla.moneda, itemtabla.cantidad, itemtabla.impuesto, itemtabla.descuento).subtotal }}</td>
<td class="text-right">{{ preciosegunmoneda(itemtabla.precio, itemtabla.moneda, itemtabla.cantidad, itemtabla.impuesto, itemtabla.descuento).igv }}</td>
<td class="text-right">{{ preciosegunmoneda(itemtabla.precio, itemtabla.moneda, itemtabla.cantidad, itemtabla.impuesto, itemtabla.descuento).total }}</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="2" class="text-right" id="cantidadItems">{{ itemstabla.length }} </td>
<td class="small font-weight-bolder">ítem(s)</td>
</tr>
</tfoot>
</table>
<!-- FIN DE SECCION SELECCIONAR ITEM Y AGREGAR A LA TABLA -->
</div>
</template>
<script>
import {
required,
minLength,
maxLength,
between
} from 'vuelidate/lib/validators'
import Bloodhound from 'corejs-typeahead/dist/bloodhound';
import typeahead from 'corejs-typeahead/dist/typeahead.jquery';
export default {
name: 'tabla-item',
props: {
tienda: Number,
},
data() {
return {
resource: 'venta',
error: false,
submitStatus: null,
isLoading: false,
fullPage: true,
q: '',
suggestions: null,
item: {},
}
},
created() {
},
mounted() {
let contextoVue = this
// Cargar los datos del typeahead en items
this.suggestions = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('title'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
identify: function (item) {
return item.id;
},
remote: {
url: '/item/records/' + this.tienda + '/' + '%QUERY',
wildcard: '%QUERY'
}
});
let inputEl = $('.prefetch input');
inputEl.typeahead({
minLength: 1,
highlight: true,
}, {
name: 'suggestions',
source: this.suggestions,
limit: 20,
display: 'label',
templates: {
suggestion: (data) => {
let codigos = data.codigos
let nuevoscodigos = ""
codigos.forEach(function (valor, indice, array) {
nuevoscodigos = nuevoscodigos + valor.pivot.nombre + ','
});
let nuevoprecio = ((parseFloat(contextoVue.preciosegunmoneda(data.precio, data.moneda,'1',data.impuesto_id).precio) * parseFloat(data.primer_margen) / 100) + parseFloat(contextoVue.preciosegunmoneda(data.precio, data.moneda,'1',data.impuesto_id).precio)).toFixed(2)
return `<div class="ss-suggestion">
<div class="codigos">
${data.nombre_marca} <span title="Código global"> - ${nuevoscodigos}</span>
<span title="Código de barras"></span>
</div>
<div class="searchproducto" style="text-transform: capitalize;">
<strong>${data.nombre}</strong>
</div>
<div class="d-flex mt-1"><div class="searchprice text-primary">${contextoVue.moneda_id}
<span id="pricechange">${nuevoprecio}</span></div>
<div class="searchstock flex-grow-1 text-danger"><b>Stock</b>:
<b style="color: red;">${data.stock}</b><b></b></div></div></div>`;
}
}
});
// Cuando se hace click en un item
$('.prefetch input').bind('typeahead:selected', function (evt, suggestion) {
$('#editarItem').modal('show')
let preciosegunmoneda = ((parseFloat(contextoVue.preciosegunmoneda(suggestion.precio, suggestion.moneda, '1', suggestion.impuesto_id).precio) * parseFloat(suggestion.primer_margen) / 100) + parseFloat(contextoVue.preciosegunmoneda(suggestion.precio, suggestion.moneda, '1', suggestion.impuesto_id).precio)).toFixed(2)
let massegundomargen = ((parseFloat(contextoVue.preciosegunmoneda(suggestion.precio, suggestion.moneda, '1', suggestion.impuesto_id).precio) * parseFloat(suggestion.segundo_margen) / 100) + parseFloat(contextoVue.preciosegunmoneda(suggestion.precio, suggestion.moneda, '1', suggestion.impuesto_id).precio)).toFixed(2)
contextoVue.item = {
id: suggestion.id,
stock: suggestion.stock,
nombre: suggestion.nombre,
precio: preciosegunmoneda,
marca: suggestion.nombre_marca,
impuesto: suggestion.impuesto_id,
primer_margen: suggestion.primer_margen,
segundo_margen: suggestion.segundo_margen,
masprimermargen: preciosegunmoneda,
massegundomargen: massegundomargen,
codigos: suggestion.codigos,
cantidad: '1',
descuento: '0',
moneda: contextoVue.moneda_id,
unidad: suggestion.nombre_unidad,
}
});
// Fin typeahead items
},
updated() {
},
computed: {
total() {
return this.item.precio * this.item.cantidad - this.item.descuento
},
igv() {
if(this.item.impuesto=="1") {
return (parseFloat(this.total) * 18/100).toFixed(2)
}
else {
return 0
}
},
subtotal() {
return (parseFloat(this.total) - this.igv).toFixed(2)
},
tipocambio: {
get() {
return this.$store.state.venta.tipocambio
},
set(value) {
this.$store.commit('settipocambio', value)
}
},
moneda_id: {
get() {
return this.$store.state.venta.moneda_id
},
set(value) {
this.$store.commit('setmonedaid', value)
}
},
itemstabla: {
get() {
return this.$store.state.itemstabla
},
},
impuestos: {
get() {
return this.$store.state.impuestos
},
},
},
watch: {
moneda_id: function (val) {
//this.$store.commit('actualizaritemstabla')
},
},
methods: {
submit() {},
preciosegunmoneda(precio, moneda, cantidad, impuesto, descuento) {
let total,nuevoprecio,subtotal,igv = 0
if ((this.moneda_id == 'S/' && moneda == 'S/') || (this.moneda_id == '$' && moneda == '$')) {
nuevoprecio = parseFloat(precio).toFixed(2)
total = parseFloat((parseFloat(precio) * cantidad) - descuento).toFixed(2)
igv = parseFloat((parseFloat(total) * (impuesto=='1' ? 18/100 : 0))).toFixed(2)
subtotal = parseFloat(total - igv).toFixed(2)
return {precio: nuevoprecio, total: total, igv: igv, subtotal: subtotal }
}
else if (this.moneda_id == 'S/' && moneda == '$') {
nuevoprecio = parseFloat(precio * this.tipocambio).toFixed(2)
total = parseFloat((parseFloat(precio * this.tipocambio) * cantidad) - descuento).toFixed(2)
igv = parseFloat((parseFloat(total) * (impuesto=='1' ? 18/100 : 0))).toFixed(2)
subtotal = parseFloat(total - igv).toFixed(2)
return {precio: nuevoprecio, total: total, igv: igv, subtotal: subtotal }
}
else if (this.moneda_id == '$' && moneda == 'S/') {
nuevoprecio = parseFloat(precio / this.tipocambio).toFixed(2)
total = parseFloat((parseFloat(precio / this.tipocambio) * cantidad) - descuento).toFixed(2)
igv = parseFloat((parseFloat(total) * (impuesto=='1' ? 18/100 : 0))).toFixed(2)
subtotal = parseFloat(total - igv).toFixed(2)
return {precio: nuevoprecio, total: total, igv: igv, subtotal: subtotal }
}
},
deleteitem(i) {
this.$store.commit('EliminarItemTabla', i);
},
editaritem(item) {
$('#editarItem').modal('show')
this.item = item
},
anadiritem(event) {
$('#editarItem').modal('hide')
this.$store.commit('setitemstabla', this.item )
},
},
validations: {
form: {
nombre_codigo: {
required,
},
}
}
}
</script>
With the use of editaritem like you do, I think you're actually passing the item itself, so it gets modified as it's a reference.
editaritem(item) {
$('#editarItem').modal('show')
this.item = { ...item } // or some way to copy the object
}
And after you finish edit, perform the actual update of the edited object.

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

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()
})

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.