Vue.js + Materialize.js issue with select input reactivity - select

I have this code with two forms.
The first form has a text input field for adding group names.
<form v-on:submit.prevent='addGroup'>
<div class='input-field'>
<label>Name</label>
<input type='text' v-model='group_name'>
</div>
As the code shows, on the form submit the function addGroup will be executed:
<script>
const vm = new Vue({
el: '#app',
data: {
group_name: '',
groups: []
},
methods: {
addGroup() {
this.groups.push(this.group_name);
this.group_name= '';
}
}
});
</script>
The function addGroup only pushes the name from the text input to the groups Array.
The second form has a select field that should be updated by the values in the groups Array:
<select>
<option v-for='group in groups'>{{ group }}</option>
</select>
This works without Materialize, but when I add Materialize to style the select field:
document.addEventListener('DOMContentLoaded', () => {
M.FormSelect.init(document.querySelectorAll('select'));
});
Materialize is not only styling the select input, it is turning it into a <ul>. And this prevents the values added to groups from apearing in the select options.
Does anyone knows a way around this?

Related

Correct way to pass different v-model values for same parameter name in POST request

FIDDLE
I am assigning v-model values from a form to parameters in an api. There is only one parameter in the api that I have to assign to both inputs i.e.: name. The name is a required field.
Using v-model values of each input, if the first radiobtn is selected, it will return null for the value of name. If the second radiobtn is selected, the user must enter text into the textbox.
I need to check whether the first radio button has been clicked OR the user entered text in the textbox and successfully pass the value of the first radiobtn or the textbox to the same api parameter (name).
How can I do this?
HTML:
<label class="radiogrp"><input type="radio" v-model="picked" name="default_user" value="reg" >Mary</label>
<label class="radiogrp"><input type="radio" v-model="picked" name="new_usr" value="non-reg"><input type="text" v-model="new_user" ></label>
JS:
/* API parameters
name: (string) or (null)
*/
new Vue({
el: '#app',
data: {
picked: Boolean,
new_user: ""
},
/* Two v-model form values for the one property in the API. */
submit_name(){
this.$http.post("https://jsonplaceholder.typicode.com/users",{
name: this.picked || this.new_user})
// ....then() etc.
}
});
You can add #input event which can update the new_usr field on input changes.
Also you can watch picked variable to make sure if you toggle between radio buttons, new_usr should be reset.
function callMe(){
var vm = new Vue({
el : '#root',
data : {picked : '',new_usr:''},
methods: {
callApi(){
let dataApi={}
if(this.new_usr.length){
dataApi.name=this.new_usr;
}else{
dataApi.name=this.picked;
}
console.log('You selected=', dataApi.name)
}
},
watch:{
picked: function (val) {
this.new_usr='';
}
}
})
}
callMe();
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.11/dist/vue.js"></script>
<div id='root'>
<div>
<label class="radiogrp">
<input type="radio" v-model="picked" name="default_user" value="reg" >Mary</label>
<label class="radiogrp">
<input type="radio" v-model="picked" name="new_usr" value="non-reg">
<input type="text" #input="evt=>new_usr=evt.target.value" :value="new_usr" > </label>
<button #click="callApi">hit me</button>
<span>Picked: {{ new_usr }}</span>
</div>
</div>

How to create a nested form in vue with json?

In Vue, how do I create a form nested in an optional object?
The nested form has to be shown based on the selected value from a dropdown.
How can I achieve this?
Vue makes this kind of thing much simpler, but straight away you're into the question of how to communicate between components. There are events and props and all kinds of things, but the best way is to have a global state object. The value selected in the dropdown is needed by the form, so it's shared state, so it goes in the global store. Then the form just looks at the store to see whether to show itself. Code working...
var vueStore = {
ddSelection : null
}
Vue.component('my-form',{
inject: ['vueStore'],
template : `
<form v-if='vueStore.ddSelection === "showForm"'>
<input type='text' placeholder='tell me everything'/>
</form>
`
});
Vue.component('my-ddown',{
inject: ['vueStore'],
template : `
<select v-model='vueStore.ddSelection'>
<option value='dontShowForm'>Don't show the form</option>
<option value='showForm'>Show the form</option>
</select>
`,
});
var vm = new Vue({
data: {vueStore: vueStore},
el: '#vueRoot',
provide: {vueStore : vueStore}
});
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<div id='vueRoot'>
<my-ddown></my-ddown>
<br>
<my-form></my-form>
</div>

Not all fields are available after submit form POST

I have a simple form with a 4 fields and submit button:
Name (text)
Surname (text)
Region (select)
City (select)
Submit (button)
For fields "Region" and "City" option-data loaded dynamically by ajax from the server.
When I fill all fields and press "Submit", I see that not all data was sent.
Only text fields were sent (Name and Surname), but select fields (which option data loaded dynamically ) - are not.
Why?
function changeCities(region) {
$.ajax({
url: "/comment/",
context: document.body,
data: {"region": region},
success: function(response){
var resp_data = JSON.parse(response);
$.each(resp_data, function (i, item) {
$('#city').append($('<option>', {
value: i,
text: item
}));
});
}
});
};
<form action="#" method="POST">
<input type="text" name="name" id="surname"><br>
<input type="text" name="surname" id="name"><br>
<select id="region" onchange="changeCities(this.value)">
<option value="default"></option>
</select><br>
<select id="city">%s</select><br>
<input type="submit" value="Submit" id="button"><br><br>
</form>
FInally I found solution! Thanks for this question: Missing fields when submitting form
Not all fields submitted when you forgot specify "name" parameter.

VueJS with Large Forms

I have a huge form with 20+ fields. i feel so much redundancy on the code i write now. What is the best way ?
<script>
new Vue({
data : {
user : {
first_name : "",
last_name : "",
username : "",
and 20+.........
}
}
});
</script>
<form>
<input name="first_name" v-model="first_name">
<input name="last_name" v-model="last_name">
<input name="username" v-model="username">
and 20+......... input fields
</form>
i feel something like this would be nice. the user object will be created dynamically.. is this possible ?
<script>
new Vue({
data : {
user : Object
}
});
</script>
<form v-model="user">
<input name="first_name">
<input name="last_name">
<input name="username">
and 20+......... input fields
</form>
Thank you in advance
Completely Redone in Vue 2
Your approach is the reverse of the usual Vue approach, in that you want to lay out your data structure in the view and have Vue pick it up, rather than laying it out in the data and having Vue render it. I can see how that would be desirable if you have a customized layout you want to achieve.
Unconventional needs require unconventional solutions, so this approach is unconventional. In particular, it is not generally recommended that a child component modify data in a parent.
That said, the approach is to create a component that will accept the form inputs as a slot and the parent object as a prop. In mounted, it gets all the input fields with name attributes and
Creates the member in the parent object, using $set
Sets a watch on the newly-created member
Adds an input event listener to complete the two-way binding
You would probably want to add more props to the component, to make the form itself more versatile, but this gives you the functionality you're looking for.
Vue.component('autoBindingForm', {
template: '<form><slot></slot></form>',
props: ['createIn'],
mounted() {
const inputs = this.$el.querySelectorAll('input[name]');
for (const i of inputs) {
this.$set(this.createIn, i.name, i.value);
this.$watch(`createIn.${i.name}`, (newVal, oldVal) => {
i.value = newVal;
});
i.addEventListener('input', (e) => {
this.createIn[i.name] = i.value;
});
}
}
});
const vm = new Vue({
el: '#app',
data: {
user: {}
}
});
// Testing that binding works both ways
setTimeout(() => {
vm.user.last_name = 'Set it';
}, 800);
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="app">
<auto-binding-form :create-in="user">
<input name="first_name" value="hi">
<input name="last_name">
<input name="username">
<div>{{Object.keys(user)}}</div>
<div>{{Object.values(user)}}</div>
</auto-binding-form>
</div>
How about
data: {
fields: { name: {v:''}, surname: {v:''}, ... }
}
and
<input v-for="(val, prop) in fields" :name="prop" v-model="val.v" />
?
https://jsfiddle.net/gurghet/dhdxqwjv/1/

Angular Access to Forms Within Directives

I am trying to create some directives for drop down lists: <drop-down-list />.
First, I am not sure how to give each <select> a unique name. Other SO answers suggest that I can't dynamically name the controls and to instead wrap every <select> in its own <form> (or ng-form).
I cannot perform validation because within my directive's $scope I don't have access to the form. Is there a reason why forms aren't showing up in directive scopes? Should I be wrapping each <select> in its own form?
This is the template HTML:
<div class="row">
<div class="col-md-12">
<form name="formDDL" novalidate>
<div class="col-md-6">
<label for="ddl">{{title}}</label>
</div>
<div class="col-md-6" data-ng-class="getControlStatus(formDDL.ddl)">
<select
name="ddl"
data-ng-options="i.{{keyField}} as i.{{textField}} for i in itemSource | orderBy: orderBy"
data-ng-model="model"
data-ng-required="isRequiredCallback"
class="form-control">
<option value="">{{defaultText}}</option>
</select>
<span data-ng-show="hasRequiredError(formDDL.ddl)" class="error">{{getRequiredErrorMessage()}}</span>
</div>
</form>
</div>
</div>
And this is the directive code:
application.directive('dropDownList', ['baseUrl', function (baseUrl) {
return {
restrict: 'E',
templateUrl: baseUrl + '/Content/templates/dropdownlist.html',
transclude: false,
replace: true,
scope: {
title: '#',
orderBy: '#',
keyField: '#',
textField: '#',
defaultText: '#',
requiredMessage: '#',
model: '=',
itemSource: '=',
isRequired: '=',
enableValidation: '='
},
controller: function ($scope) {
$scope.isRequiredCallback = getCallback($scope.isRequired, false);
$scope.isValidationEnabled = getCallback($scope.enableValidation, false);
$scope.getControlStatus = function (control) {
if (!$scope.isValidationEnabled() && !control.$dirty) {
return {};
}
return {
'has-success': !control.$error.required,
'has-error': control.$error.required
}
};
$scope.hasRequiredError = function (control) {
if (!$scope.isValidationEnabled() && !control.$dirty) {
return false;
}
return control.$error.required;
};
$scope.getRequiredErrorMessage = function () {
return $scope.requiredMessage;
};
}
};
}]);
You can ignore the getCallback function: it just handles the bound value being a boolean or a function.
It turns out I had a combination or issues.
Originally, I thought I could change the name of the control. However, you can't give inputs dynamic names: Dynamic validation and name in a form with AngularJS.
The trick is to give each control a nested form (or ng-form) and reuse the same name.
Well, Chrome was caching my template, so the broken HTML was being returned. I set up Chrome developer tools to never cache.
The next problem was that an <input> must have ng-model defined in order to show up under the form object (FormController). That makes logical sense: http://docs.angularjs.org/guide/forms#binding-to-form-and-control-state.