I have a dynamic form which contains a list of elements specified in the following way:
element1: { name: "property1", value: "value1", defaultValue: "defaultvalue1", usingDefaultValue: true, type: "String/Enum/Int/Long }
This list of elements is constantly published via a WebSocket and stored in a Vuex store. Each element corresponds to an input, which displays either the value or the defaultValue, depending on a toggle switch. These values are bound to the input fields using v-model.
I use the vuex-map-fields library to help with the dynamic multi row fields. They are defined as follows:
computed: {
...mapMultiRowFields(`elementsStore`, ["elements"])
},
The fields are passed into a list component using a v-for:
<v-layout v-for="element in elements" :key="element.name">
<element-list-item :element="element"></element-list-item>
</v-layout>
Now, the following works perfectly as I expect:
<v-text-field
v-model="element.useDefaultValue ? element.defaultValue : element.value"
</v-text-field>
However, whenever I use npm run lint, I get the following error message:
error: 'v-model' directives require the attribute value which is valid as LHS (vue/valid-v-model)
Is there a better way to achieve this same behaviour?
I'm going to assume the error message is there for a good reason.
One alternative approach I have tried is using a computed property. However, this did not work and immediately displayed errors in the console.
Related
I am using vue-multiselect like so:
<multiselect
id="customer_last_name_input"
v-model="value"
:options="activeUserProfiles"
label="lastname"
placeholder="Select or search for an existing customer"
track-by="uid"
:close-on-select="true"
#select="onSelect"
#remove="onRemove"
:loading="isLoading"
:custom-label="customerSelectName"
aria-describedby="searchHelpBlock"
selectLabel=""
>
...that grabs the list of active customers from an Array and then makes them available in a nice select menu.
This works good. However, I need to add another option from another resource (called customerNone) to the options prop and but the data is returned as an Object like so:
{"uid":1,"lastname":"None Given","firstname":"User","email":null,"phone":null...blah}
The vue-multiselect docs state that the :option prop MUST be an Array.
Question: What is the best way for me to handle this in the vue-multiselect component? Here's my attempt to help explain what I am trying to do (not sure if this is the best way to handle it). Unfortunately, my attempt causes a console error (see below):
I am passing a prop down called noCustomer which, if is true, I need to use customerNone profile on :options:
<multiselect
:options="noCustomer ? customerNone : getActiveUserProfiles"
>
here's the error:
Invalid prop: type check failed for prop "options". Expected Array, got Object
Is there a way I can convert the customerNone object to an array of object? Thanks!
You could wrap the customerNone object in brackets at the time that you pass it to the <multiselect> like [customerNone].
This syntax creates a new array on the fly, having 1 element that is the object variable:
<multiselect
:options="noCustomer ? [customerNone] : getActiveUserProfiles"
>
Update for comments
In order to auto-select the generic option when it's available, use a watch on the noCustomer prop to set value whenever noCustomer === true:
watch: {
noCustomer(newValue, oldValue) {
if(newValue) { // Checking that `noCustomer === true`
this.value = this.customerNone;
}
}
}
I recently experienced a bug in my vue.js application. I was able to fix it, and it taught me something about how vue.js works. I want to know if I'm corret.
First of all, the bug:
props: {
property: {},
...
}
data(): {
return {
propertyData: {
field1: null,
field2: '',
field3: [{email: null, notifying: false}]
},
...
};
}
created() {
this.propertyData = JSON.parse(JSON.stringify(this.property));
}
I was trying to make a copy of the property prop and assigned that to propertyData as a means of being able to mutate propertyData without mutating the property prop directly. But this was causing issues in the template. It almost seemed like there was a disconnect between the propertyData object and whatever the template was binding to.
For example, I would add this to the template:
{{propertyData.field3}}
...and it would print to the screen:
[{email: null, notifying: false}]
...which is expected at first. But I have an email text field that binds to the email field in each object of field3:
<v-layout v-for="(item, index) in propertyData.field3" :key="index">
<v-flex>
<v-text-field
label="Email"
v-model="item.email"
placeholder="username#example.com"
:rules="[v => !!v || 'Email is required',
v => /.+#.+/.test(v) || 'Email is invalid' ]"
maxlength="255"
required>
</v-text-field>
</v-flex>
</v-layout>
When I type something into the email field, the propertyData array doesn't update on the screen... that is, until the email field is validated (for example, when I finally add # and the first character of the domain).
I would put console logs in the code and it would print out the correct output for propertyData. That is, it would print out exactly what I currently had in the email field even though, on the screen, it didn't.
Then I tried this instead:
created() {
Object.assign(this.propertyData, {...this.property});
}
This fixed the problem. Now the propertyData.field3 array prints the actual current data entered into the email field and updates immediately.
So the lesson I learned from this (and this is what I'd like someone to confirm) is that the object the template binds to is not the data object (not directly at least). Once the component is created (or maybe mounted), any references to propertyData in the template, or to any of its fields, refers to the initial object defined in data() (with null for field1, '' for field2, etc.). But if you assign a completely different object to propertyData after that (which is what this.propertyData = JSON.parse(JSON.stringify(this.property)) would do), the template doesn't change the object it binds to. It's still bound to the initial object (with null for field1, '' for field2, etc.) and it requires a rendering update (which validation would do) to update the object the template binds to to the object propertyData refers to. My fix worked because Object.assign(...) doesn't change the object propertyData refers to, it just changes (or adds to) the fields.
^ Is this correct?
I am looking to use custom Javascript to interact with form fields tied with Vue framework. The form appears in a WordPress theme search page (https://wilcity.com/search-without-map/)
Autoselect the region value (this I can perform using the JS below)
markerCityName = "Atlanta";
for (i = 0; i < document.getElementsByClassName("wilcity-select-2 select2-hidden-accessible")[0].options.length; i++) {
if (document.getElementsByClassName("wilcity-select-2 select2-hidden-accessible")[0].options[i].innerText == markerCityName) {
document.getElementsByClassName("wilcity-select-2 select2-hidden-accessible")[0].selectedIndex =
document.getElementsByClassName("wilcity-select-2 select2-hidden-accessible")[0].options[i].index;
triggerEvent(document.getElementsByClassName("select2-selection select2-selection--single")[0], 'focus');
triggerEvent(document.getElementsByClassName("select2-selection select2-selection--single")[0], 'keydown');
triggerEvent(document.getElementsByClassName("wilcity-select-2 select2-hidden-accessible")[0], 'change');
}
}
Selected value participates in the search without having to manually select it from the form interface.
(i) After the javascript runs and selects "Atlanta" in the region drop down.
(ii) Select any other field in the form for search to be executed.
(iii) you will notice this search did not take into account the pre-select region value "Atlanta"
I am unable to do (2). The autoselected value is not sent in post when form value changes, and the autoselected value is not picked up.
Modifying the DOM directly won't work, as you've discovered, because Vue doesn't know about those changes and is still working based on its internal state. You need to modify Vue's underlying data model instead.
Every Vue component's root DOM element will have a __vue__ property attached, which you can use to access and modify the component's internal state from outside:
// Set up a Vue component with some data in it:
Vue.component('child', {
data() {
return {
foo: 'Data from inside Vue'
}
},
template: '<div id="component">{{foo}}</div>'
})
new Vue({
el: '#app',
});
// now outside Vue:
document.getElementById('component').__vue__.$data.foo = "Updated value from outside vue"
<script src="https://unpkg.com/vue#latest/dist/vue.min.js"></script>
<div id="app">
<child></child>
</div>
Use of the __vue__ property isn't officially supported, as far as I know, but Vue's author says it's safe to use:
the official devtool relies on it too, so it's unlikely to change or break.
When I'm using ng-select in reactive form angular I get this error:
ERROR TypeError: selectedItems.map is not a function
I have 3 select the first two work very well but in this third one I get the error ! to map item i'm using the function (the ng-select is inside *ngFor) :
//for mappinig item :
mapLabelValueBS(objet) {
return objet.map(data => {
return {
id: data,
text: data.name
}
})
}
//this is the one that is causing the problem
<ng-select
[allowClear]="true"
[items]="mapLabelValueBS(filieres)"
placeholder="Filière non sélectionné"
(selected)="selecteFiliere($event)"
formControlName="filiere">
</ng-select>
the result in my page (when I click on the field it doubles itself) :
Without the code is difficult to know, but today I had the same error. The reason was that I determined a default value in the FormControl that had no relation with the array that ng-select demands. When the FormGroup loaded, and this mistaken default was loaded into the ng-select, the error was selectedItems.map is not a function
This error came while I was passing [items]="value" where the value was not an array, so please check if you are not passing non array element to items binding.
You are trying to bind the items of object type. [items] attribute accepts an array. You can trying adding a pipe keyvalue
<ng-select
[allowClear]="true"
[items]="jsonData | keyvalue"
placeholder="Filière non sélectionné"
(selected)="selecteFiliere($event)"
formControlName="filiere">
</ng-select>
few days ago i came across this error if you are binding a list that is filled from backend server be sure to fill the list using concat method like this
this.userService.getLookup().subscribe((res: any) => {
this.apps = this.apps.concat(res.data);
});
I had same problem, because the list of items was undefined sometime in the middle of page preparing, so I added a silly condition to show those select only when the list of items is ready:
<ng-select
*ngIf="selectedItems.map"
[allowClear]="true"
[items]="jsonData | keyvalue"
placeholder="Filière non sélectionné"
(selected)="selecteFiliere($event)"
formControlName="filiere">
</ng-select>
I have just started with Angular 4 and I need to develop a CRUD grid, where the user can add, edit or delete rows.
During my research I found this article where it shows how to create the grid and also the actions: Angular 4 Grid with CRUD operations.
Looking at his code, what called my attention was the way he is using the ng-template to toggle between edit/view mode.
<tr *ngFor="let emp of EMPLOYEES;let i=idx">
<ng-template [ngTemplateOutlet]="loadTemplate(emp)" [ngOutletContext]="{ $implicit: emp, idx: i }"></ng-template>
</tr>
On the article he uses template driven forms to edit the row. However, I was trying to change to reactive forms.
In my attempt to do that, I tried to replace the [(ngModel)] to formControlName and I got some errors. My first attempt I tried to add the [formGroup] at the beginning of the template html inside form element. But when I tried to run and edit the row, I got the following error:
Error: formControlName must be used with a parent formGroup directive. You'll want to add a formGroup directive and pass it an existing FormGroup instance (you can create one in your class).
When I tried to move the [formGroup] inside the ng-template it works, however I was not able to bind the value to the fields and I had to set the values in the loadTemplate function:
loadTemplate(emp: Employee) {
if (this.selemp && this.selemp.id === emp.id) {
this.rForm.setValue({
id: emp.id,
name: emp.name
});
return this.editTemplate;
} else {
return this.readOnlyTemplate;
}
}
This works and show the values inside the fields in a read only mode :(
Here is the Plunker of what I have got so far.
How can I make a reactive form work with ng-template and how to set values to edit the entries?
Any help is appreciated! Thanks
Actually your form is not readonly, you are just constantly overwriting the input you are entering. Since you are having a method call in template (which is usually not a good idea), loadTemplate gets called whenever changes happen, which in it's turn means that
this.rForm.setValue({
id: emp.id,
name: emp.name
});
gets called over and over whenever you try and type anything. We can overcome this with instead setting the form values when you click to edit. Here we also store the index so that we can use it to set the modified values in the correct place in array, utilizing the index could perhaps be done in a smarter way, but this is a quick solution to achieve what we want.
editEmployee(emp: Employee) {
this.index = this.EMPLOYEES.indexOf(emp)
this.selemp = emp;
this.rForm.setValue({
id: emp.id,
name: emp.name
});
}
so when we click save, we use that index...
saveEmp(formValues) {
this.EMPLOYEES[this.index] = formValues;
this.selemp = null;
this.rForm.setValue({
id: '',
name: ''
});
}
Your plunker: https://plnkr.co/edit/6QyPmqsbUd6gzi2RhgPp?p=preview
BUT notice...
I would suggest you perhaps rethink this idea, having the method loadTemplate in template, will cause this method to fire way too much. You can see in the plunker, where we console log fired! whenever it is fired, so it is a lot! Depending on the case, this can cause serious performance issues, so keep that in mind :)
PS. Made some other changes to code for adding a new employee to work properly (not relevant to question)