I'm really struggling on how to implement a pattern inline with the "Vue philosophy".
Imagine a list of items that you'd like to edit. There are numerous examples of how to edit those items "inline". But I'd like to edit my items through the same, persistent form. So when you click a list item, the form input "rebinds" to clicked list item.
Here's a working example: http://jsbin.com/sopakid/3/edit?html,js,output which uses a method (updateRecord) to copy the form input (bound to editRecord), to the referenced li's data binding (messages[index]).
data: {
messages: [
{ name: "Dale Cooper", message: "Black as midnight on a moonless night" },
{ name: "Shelly Johnson", message: "I've got one man too many in my life and I'm married to him." },
{ name: "Sheriff Truman", message: "Jelly donuts?"}
],
editRecord:
{ name: "", message: "" }
},
updateRecord: function(){
var index = this.editRecord.index;
this.messages[index].name = this.editRecord.name;
this.messages[index].message = this.editRecord.message;
}
Looking for any insight as to how to better implement this pattern. Thanks!
Idiomatically, you don't really need any methods in this case. You can set the editRecord directly in the template. Additionally, if you set the editRecord to the selected message, then you don't even need an update button or a need to keep track of the index.
var app = new Vue({
el: '#app',
data: {
editRecord:{ name: null, message: null },
messages: [
{ name: "Dale Cooper", message: "Black as midnight on a moonless night" },
{ name: "Shelly Johnson", message: "I've got one man too many in my life and I'm married to him." },
{ name: "Sheriff Truman", message: "Jelly donuts?"}
]
}
});
These are the changes I made to your template
<div id="app">
<div class="messages">
<ul>
<li class="message" v-for="(message, index) in messages" #click="editRecord = message">
<div class="message__name">
{{message.name}}
</div>
<div class="message__body">
{{message.message}}
</div>
</li>
</ul>
</div>
<div class="edit-form" v-if="editRecord">
<label for="name">Name</label>
<input name="name" type="text" placeholder="Name" v-model="editRecord.name">
<label for="message">Message</label>
<textarea name="message" id="" cols="30" rows="6" v-model="editRecord.message"></textarea>
</div>
</div>
Here is your fiddle updated.
The result takes advantage of Vue's reactivity and as you edit in your form you can see the changes taking place in the list as well.
Some people may like the approach you've taken though, where the edits they make are not complete until they specify they are. A lot of this is a matter of taste.
I would warn against using index to track your selected record, however. If the list changes underneath you, the indexes change. Use an id property on your messages. If you go with the reactive approach though, you don't really need that.
Related
The first time that I dive into making this type of form with Vue, the issue is that I can't think of how to save the data inside the foreach that I generate with axios.
Where I would like to save the ID and the option selected with the input select as an object in order to make faster the match in the backend logic.
<template>
<div class="row" v-else>
<div class=" col-sm-6 col-md-6 col-lg-6" v-for="(project, index) in projects" :key="index">
<fieldset class="border p-2 col-11">
<legend class="w-auto col-12">Proyecto: {{project.name}}</legend>
<b-form-group
id="user_id"
label="Reemplazante"
>
<b-form-select
v-model="formProject[index].us"
:options="project.users"
value-field="replacement_user_id"
text-field="replacement_user_name"
#change="addReemplacemet($event,project.id)"
>
<template v-slot:first>
<b-form-select-option value="All">Seleccione</b-form-select-option>
</template>
</b-form-select>
<input type="hidden" name="project" v-model="formProject[index].proj">
</b-form-group>
</fieldset>
</div>
</div>
</template>
<script>
import skeleton from './skeleton/ProjectUserSkeleton.vue'
export default {
name: 'ProjectsUser',
components: { skeleton },
props:{
user: { type: String },
},
data() {
return {
user_id: null,
showProject: false,
projects: [],
loadingProjects: true,
formProject: [
{
us: 'All',
proj: null
}
]
}
},
watch: {
user: function() {
this.viewProjects(this.user)
}
},
methods: {
async getProjects(salesman){
this.loadingProjects = true
await axios.get(route('users.getProjects'),{
params: {
filter_user: salesman
}
})
.then((res)=>{
this.projects = res.data.data
setTimeout(() => {
this.loadingProjects = false
}, 800);
})
},
This is the form:
This is the message error:
At first glance, this looks like an issue with data. Your error suggests that you're trying to read an undefined variable.
It's often undesirable to use an index from iterating one array on another. In your Vue code, the index is projects, but you use it to access the variable formProject. This is usually undesirable because you can no longer expect that index to reference a defined variable (unless you're referencing the array you are currently iterating).
The easiest solution, for now, is to make sure the arrays are of the same length. Then utilizing v-if or other methods to not render the snippet if the variable is not defined.
A more complicated but better solution is restructuring your data such that formProject exists in projects
I'm relatively new to Vue so bare with me on this.
I have a v-for which produces a list of checkboxes, these all individually function correctly if I were to click on them separately, what I am after, like the title says is a select all checkbox to sit ontop which selects all in the list, but I'm running into a few issues which I don't understand, see relevant code below:
new Vue({
el: "#lister-app",
mixins: [pagination, baseDownloadParent],
components: { selectField, articleItem, documentItem },
data: {
facets: {},
isCheckAll: false,
},
computed: {
getFacets() {
return this.facets;
},
getFacetsLength() {
return this.facets.length;
},
},
methods: {
toggleSelect(item, facet) {
if (!this.params[facet.name]) Vue.set(this.params, facet.name, []);
const existElem = this.params[facet.name].findIndex((el) => {
return el === item.identifier;
});
if (existElem !== -1) this.params[facet.name].splice(existElem, 1);
else this.params[facet.name].push(item.identifier);
},
checkAll(){
console.log('FunctionWhichChecksAll');
},
},
});
<label class="option-checkbox" for="Select all">
<input id="Select all" class="option-checkbox__input" type="checkbox" #click='checkAll()' v-model='isCheckAll'>
<span class="option-checkbox__text">Select all</span>
<span class="option-checkbox__icon"></span>
</label>
<option-field inline-template v-for="(item, i) in facet.items" :for="item.name" :key="i + item.name">
<label class="option-checkbox">
<input :id="item.name" class="option-checkbox__input" type="checkbox" v-model="checked" #change="toggleSelect(item, facet)">
<span class="option-checkbox__text">{{item.name}} </span>
<span class="option-checkbox__icon"></span>
</label>
</option-field>
What I am picturing is as piece of script which is inside the checkAll() function?
Any help would be appreciated, thank you in advance.
Here is a working demo, based on your code (simplified):
https://stackblitz.com/edit/vue-prghrq?file=src%2FApp.vue
Checkboxes in Vue can sometimes be a mess, when you combine v-model with #click handlers and especially if you have a check-all field. That's why I usually don't use v-models for it. Using only the :checked value as a one-way binding makes it easier to read and to maintain. With the click handler you can then update the state of each entry.
The following code is supposed to list a series of tasks according to their status of completion. It works just fine when I use a 2.5.xx Vue cdn link.
However with the current version's cdn (>= 2.6.0), whenever I check/uncheck a task from either list, the next item on the list is checked/unchecked too, even though it's completed status attribute is not affected (I can see it with the Vue Chrome extension) unless I click on it again.
<div id="root">
<h3>Incomplete Tasks</h3>
<ul>
<li v-for="task in incompleteTasks">
<input type="checkbox" v-model="task.completed"> {{ task.description }}
</li>
</ul>
<h3>Completed Tasks</h3>
<ul>
<li v-for="task in completedTasks">
<input type="checkbox" v-model="task.completed"> {{ task.description }}
</li>
</ul>
</div>
new Vue({
el: '#root',
data: {
tasks: [{
description: 'Go to the store',
completed: true
},
{
description: 'Finish screencast',
completed: false
},
{
description: 'Make donation',
completed: false
},
{
description: 'Clear inbox',
completed: false
},
{
description: 'Make dinner',
completed: false
},
{
description: 'Clean room',
completed: true
},
]
},
computed: {
completedTasks() {
return this.tasks.filter(task => task.completed);
},
incompleteTasks() {
return this.tasks.filter(task => !task.completed);
},
},
});
Is this a bug? Did something change in how we should use v-model?
Here's a fiddle reproducting the issue using Vue 2.6.10
https://jsfiddle.net/flayshon/fd7mejvo/2/
As pointed out by thanksd, including a :key to v-for solves the issue.
In the documentation, it says:
This default mode is efficient, but only suitable when your list render output does not rely on child component state or temporary DOM state (e.g. form input values).
I guess the checkbox was the child component in this case, but still not sure as to why it used to work prior to version 2.6
JSBin and Stackoverflow snippet are below.
I am trying to have a list of input components. If all input components are filled with a value (not blank), then there should be a "new blank field" visible at the end for the user to type into. When he types into it, it should make this field apart of the list above it, maintaining focus in it.
However the problem I'm having is, focus maintains in the new field, and never moves into the array. Here is my code:
JSBIN and stackoverflow snippet - https://jsbin.com/cudabicese/1/edit?html,js,output
const app = new Vue({
el: '#app',
data: {
inputs: [
{ id:'foo', value:'foo' },
{ id:'bar', value:'bar' }
]
},
methods: {
addRow(e) {
this.inputs.push({
id: Date.now(),
value: e.target.value
})
},
deleteRow(index) {
this.inputs.splice(index, 1)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
<div id="app">
<ul>
<li v-for="(input, index) of inputs">
<input type="text" v-model="input.value">
</li>
<li v-if="inputs.filter(input => !!input.value).length">
<input type="text" #input="addRow">
</li>
</ul>
</div>
I'd recommend you put the input for the list within a computed function vs directly using the data. The examples at https://v2.vuejs.org/v2/examples/ are a good place to start.
I tried to stay away from the Vue components of Spark as much as possible but I discovered I had to implement a certain mail settings so I can't hold it much longer.
Luckily the Spark documentation contains a small cookbook for adding profile fields:
https://spark.laravel.com/docs/4.0/adding-profile-fields
Most parts are within my (limited PHP) comfort zone:
First the blade php:
Mail settings
<div class="col-md-6">
<label class="radio-inline"><input type="radio" value="profile" v-model="form.type" name="profile">Profile</label>
<label class="radio-inline"><input type="radio" value="website" v-model="form.type" name="website">Website</label>
<label class="radio-inline"><input type="radio" value="combined" v-model="form.type" name="combined">Combined</label>
<span class="help-block" v-show="form.errors.has('mail-settings')">
#{{ form.errors.get('mail-settings') }}
</span>
</div>
</div>
Which is integrated:
<!-- Update Mail settings -->
#include('settings.profile.update-mail-settings')
So as can be seen in the previous code block, I wish to store the result of 3 radio buttons.
However the linked Vue js file is giving my headaches:
Vue.component('update-mail-settings', {
props: ['user'],
data() {
return {
form: new SparkForm({
profile: ''
website: ''
combined: ''
})
};
},
mounted() {
this.form.mailsettings = this.user.mailsettings;
},
methods: {
update() {
Spark.put('/settings/profile/mail-settings', this.form)
.then(response => {
Bus.$emit('updateUser');
});
}
}
});
But how on earth do I integrate the radio buttons in the SparkForm?
In Vue, data binding occurs when you v-model to the object by name. Or in other words, you call v-model="object.property" on an input. When the user fills out the form, the value of form.type will match the form input. So simply change your form object to read:
data() {
return {
form: new SparkForm({
type: '' <- this can now be v-modeled to "form.type"
})
};
},
Your radio buttons don't need to change because they are bound correctly: v-model="form.type"
https://v2.vuejs.org/v2/guide/forms.html#Radio