vee validate 3.x setting all fields to touched on submit - vue.js

How can I set the fields in my form that are wrapped in ValidationProvider to touched when submitting a form. My current code is as follows using the ValidationObserver with a ref of "observer" to wrap the form. This changes the fields object in the observer but does not reflect in my ValidationProvider as being touched as I only show error when touched and error exists and the touched part is not being triggered.
onSubmit () {
const observer = this.$refs.observer as any
return observer.validate().then((valid: boolean) => {
if (!valid) {
for (const field in observer.fields) {
observer.fields[field].touched = true
observer.fields[field].untouched = false
}
}
return valid
})
}

Related

Binding class doesn't seem to work properly based on attached variable value

My banner-visible binding class is conditioned by my showMainBanner computed property:
<div :class="['view view-index', { 'banner-visible' : showMainBanner }]">
Here's how I define my computed property:
computed: {
showMainBanner () {
return (Cookies.get('banner_dismiss')) ? false : this.$store.state.config.showMainBanner
}
}
When I reload the page, I can see the value of my computed property by console logging it in the mounted() hook:
mounted () {
console.log('showMainBanner = ' + this.showMainBanner)
}
In the console on page load I see: showMainBanner = false
Yet, the banner-visible class is still there, despite the value of showMainBanner being false.
How is that possible?
The most puzzling thing is this: when I navigate the page away through a link, and come back to it through a link, this problem does NOT occur. But if I refresh the page, it does.
EDIT:
I changed my code to something even more explicit:
showMainBanner () {
if (Cookies.get('banner_dismiss')) {
console.log('RETURN FALSE')
return false
} else {
console.log('RETURN TRUE')
return this.$store.state.config.showMainBanner
}
}
After refreshing the page, I see in the console RETURN FALSE and I still see showMainBanner = false from the console.log() call in mounted() hook. So clearly, showMainBanner is definitely equal to false.
Is there black magic going on here?
Cookies is not reactive. After the first time showMainBanner is evaluated it does not get evaluated again by Vue.
When you navigate away and come back, Vue will evaluate the computed property again and this time the Cookie is already set.
Your issue is because your cookie returns a string:
console.log(typeof Cookies.get('banner_dismiss'))
//=> string
Try to change your condition like:
computed: {
showMainBanner () {
let dismiss = Cookies.get('banner_dismiss') === 'undefined' ? false : JSON.parse(Cookies.get('banner_dismiss'))
return dismiss ? false : this.$store.state.config.showMainBanner
}
}
Or if you want to avoid JSON's errors in case of empty Cookie, you can add a plain condition:
computed: {
showMainBanner () {
let dismiss = Cookies.get('banner_dismiss') === 'true'
return dismiss ? false : this.$store.state.config.showMainBanner
}
}

Vue.js - can't access the properties of a prop object

I am creating then passing an object using pdfjs in to a child Vue component. When I do so, I can access the object itself, but I cannot access any properties of the object.
This is the case during all of the lifecycle hooks.
<i-slide-deck-pdf // calling child vue component
v-if="true"
:slideDeckItem="fetchPDF('/static/intropdf.pdf')"
:current-user-progress="currentUserProgress"
#i-progress="putProgressTracker"
#i-slide-change="onSlideChange"
/>
...
fetchPDF(url) { // function being used to create the object
let pdfItem = new Object();
import(
'pdfjs-dist/webpack'
).
then(pdfjs => pdfjs.getDocument(url)).
then(pdf => {
pdfItem.pdf = pdf;
pdfItem.pages = range(1, pdf.numPages).map(number => pdf.getPage(number));
pdfItem.pageCount = pdfItem.pages.length;
})
return pdfItem;
},
...
props: { // prop call in child component
slideDeckItem: {
type: Object,
required: true
},
}
Console log
Thanks in advance.
This is because the async call hasn't completed, so you are just returning an empty object, to fix this you want to set a value inside the then portion of your code, and bind this to your prop, so:
fetchPDF(url) { // function being used to create the object
let pdfItem = new Object();
import(
'pdfjs-dist/webpack'
).
then(pdfjs => pdfjs.getDocument(url)).
then(pdf => {
pdfItem.pdf = pdf;
pdfItem.pages = range(1, pdf.numPages).map(number => pdf.getPage(number));
pdfItem.pageCount = pdfItem.pages.length;
// This should be inside the "then"
this.slideDeckItem = pdfItem;
})
},
You'll then want to declare slideDeckItem in your parent data property, and bind that to your component's prop:
<i-slide-deck-pdf
v-if="true"
:slideDeckItem="slideDeckItem"
:current-user-progress="currentUserProgress"
#i-progress="putProgressTracker"
#i-slide-change="onSlideChange"
/>
I've made a JSFiddle, to give you the basic idea, although I've used a timeout to simulate the async call: http://jsfiddle.net/ga1o4k5c/
You may also want to take a look at how Promises work

Events not firing when value changes in Aurelia app

I have an Aurelia app using Store to manage state between components.
In my dropdown component I have validation (not framework validation, just code which should get invoked in the ViewModel on change) which should fire when the value changes:
<select value.bind="parameter.value"
change.delegate="valueChanged()"
class.bind="isValid ? '' : 'has-error'">
...
</select>
In the ViewModel the validation works like this:
#bindable() parameter: Parameter;
parameterChanged() {
this.validate();
}
valueChanged() {
this.validate();
}
private validate() {
this.isValid = this.parameter.value != '0';
}
The Parameter model looks like:
export interface Parameter {
value: string;
...
}
The Parameter is passed down to this component by a parent component where the value can change on a state object managed with Store.
The value can change when the following action gets invoked to change the value on the State object:
export async function changeValue(state: State, value: string) {
const newState = Object.assign({}, state);
newState.setup.parameter.value = value;
return newState;
}
When the parameter value changes on the state object the dropdown visibly changes on the screen, but parameterChanged() or valueChanged() do not fire.
Does anyone know what is happening here and anything I can try to resolve this? Any help appreciated...
Because I am using Aurelia Store I should have been using a state changed subscription as follows:
#connectTo({
selector: {
parameter: (store) => store.state.pipe(pluck("parameter"))
}
})
parameterChanged() {
this.validate();
}
The reasons why this wasn't working as expected are:
valueChanged() is bound to the element's change function, since the value is changing this will not fire.
parameterChanged() does not fire because parameter hasn't changed, the value property of parameter is changing

Need the same functionality as a computed property, but I need to be able to update the data after initial change

I have a situation where I need to update data when it detects changes to a state. The user needs to be able to make further changes this info within a textarea. Using computed properties pulls in the data exactly how I want, but any changes made by the user after this are overridden because the computed property keeps changing this data back to it's initial values. What would be the best way to pull in data initially upon a state change but then allow for editing after that point?
Thanks!
Edit: Updated to what i've tried for #Libby.
<textarea v-model="exampleData"></textarea>
computed: {
...mapGetters({
item: 'item'
})
methods: {
exampleFunction() {
this.exampleData = this.item;
}
mounted() {
this.exampleFunction();
}
Update exampleData in a watcher for item:
watch: {
item(value) {
this.exampleData = value;
}
}
This way you can bind your exampleData to the textfield, but changes to the item will still affect it.
And if you want exampleData to be initially set to the value of item, do that in the component's mounted hook:
mounted() {
this.exampleData = this.item;
}
Here's a fiddle.
If you set your property indata, you can initialize it in mounted which only runs once when the page is loaded:
data:
text: null
mounted: ->
text = "This text is initialized"
And then set v-model on your textarea
<textarea v-model="text"></textarea>
So the value of the textarea will start out as "This text is initialized", but the user will be able to change it, and those changes will be saved in text
Vue already has a built-in solution to handle this if you use the getter/setter syntax for computed properties
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
As a result, when your state changes you can update the computer property by assigning it a value:
// state has changed in text area handler
this.fullName = 'new value'

Lists and Components not updating after data change - (VueJS + VueX)

A question about best practice (or even a go-to practice)
I have a list (ex. To-do list). My actual approach is:
On my parent component, I populate my 'store.todos' array. Using a
getter, I get all the To-do's and iterate on a list using a v-for
loop.
Every item is a Component, and I send the to-do item as a prop.
Inside this component, I have logic to update the "done" flag. And this element display a checkbox based on the "state" of the flag. When it does that, it do an action to the db and updates the store state.
Should I instead:
Have each list-item to have a getter, and only send the ID down the child-component?
Everything works fine, but if I add a new item to the to-do list, this item is not updated when I mark it as completed. I wonder if this issue is because I use a prop and not a getter inside the child component
Code:
store:
const state = {
tasks: []
}
const mutations = {
CLEAR_TASKS (state) {
state.tasks = [];
},
SET_TASKS (state, tasks) {
state.tasks = tasks;
},
ADD_TASK (state, payload) {
// if the payload has an index, it replaces that object, if not, pushes a new task to the array
if(payload.index){
state.currentSpaceTasks[payload.index] = payload.task;
// (1) Without this two lines, the item doesn't update
state.tasks.push('');
state.tasks.pop();
}
else{
state.tasks.push(payload.task);
}
},
SET_TASK_COMPLETION (state, task){
let index = state.tasks.findIndex(obj => obj.id == task.id);
state.tasks[index].completed_at = task.completed_at;
}
}
const getters = {
(...)
getTasks: (state) => (parentId) => {
if (parentId) {
return state.tasks.filter(task => task.parent_id == parentId );
} else {
return state.tasks.filter(task => !task.parent_id );
}
}
(...)
}
const actions = {
(...)
/*
* Add a new Task
* 1st commit add a Temp Task, second updates the first one with real information (Optimistic UI - or a wannabe version of it)
*/
addTask({ commit, state }, task ) {
commit('ADD_TASK',{
task
});
let iNewTask = state.currentSpaceTasks.length - 1;
axios.post('/spaces/'+state.route.params.spaceId+'/tasks',task).then(
response => {
let newTask = response.data;
commit('ADD_TASK',{
task: newTask,
index: iNewTask
});
},
error => {
alert(error.response.data);
});
},
markTaskCompleted({ commit, dispatch, state }, task ){
console.log(task.completed_at);
commit('SET_TASK_COMPLETION', task);
dispatch('updateTask', { id: task.id, field: 'completed', value: task.completed_at } ).then(
response => {
commit('SET_TASK_COMPLETION', response.data);
},
error => {
task.completed_at = !task.completed_at;
commit('SET_TASK_COMPLETION', task);
});
},
updateTask({ commit, state }, data ) {
return new Promise((resolve, reject) => {
axios.patch('/spaces/'+state.route.params.spaceId+'/tasks/'+ data.id, data).then(
response => {
resolve(response.data);
},
error => {
reject(error);
});
})
}
}
And basically this is my Parent and Child Components:
Task List component (it loads the tasks from the Getters)
(...)
<task :task = 'item' v-for = "(item, index) in tasks(parentId)" :key = 'item.id"></task>
(...)
The task component display a "checkbox"(using Fontawesome). And changes between checked/unchecked depending on the completed_at being set/true.
This procedure works fine:
Access Task list
Mark one existing item as done - checkbox is checked
This procedure fails
Add a new task (It fires the add task, which firstly adds a 'temporary' item, and after the return of the ajax, updates it with real information (id, etc..). While it doesn't have the id, the task displays a loading instead of the checkbox, and after it updates it shows the checkbox - this works!
Check the newly added task - it does send the request, it updates the item and DB. But checkbox is not updated :(
After digging between Vue.js docs I could fix it.
Vue.js and Vuex does not extend reactivity to properties that were not on the original object.
To add new items in an array for example, you have to do this:
// Vue.set
Vue.set(example1.items, indexOfItem, newValue)
More info here:
https://v2.vuejs.org/v2/guide/reactivity.html
and here: https://v2.vuejs.org/v2/guide/list.html#Caveats
At first it only solved part of the issue. I do not need the "hack" used after pushing an item into the array (push and pop an empty object to force the list to reload)
But having this in mind now, I checked the object returned by the server, and although on the getTasks, the list has all the fields, including the completed_at, after saving a new item, it was only returning the fields that were set (completed_at is null when created). That means that Vue.js was not tracking this property.
I added the property to be returned by the server side (Laravel, btw), and now everything works fine!
If anybody has a point about my code other than this, feel free to add :)
Thanks guys