VueJS - v-model in for-loop - vue.js

I'm trying to build something like the questions in OkayCupid, but all the questions - which are different forms - are located on the same component.
I use an object of questions and 3 possible answers for each question, and I use v-for to loop through the object and create cards with a question, 3 answers with radios, and a submit button.
The problem is that I want to get not only the answer the user chooses, but also the question it belongs to.
Here is my form in the template:
<div class="container">
<div class="row">
<div
class="col-lg-3 col-md-4 col-6"
v-for="(question,index) in questionCollection"
:key="index"
>
<form class="form">
<div class="img-fluid img-thumbnail shadow-lg p-3 mb-5 bg-white rounded">
<!-- <input type="text" :value="question.question" v-model="q" /> -->
<h3 class="d-block mb-4 h-100" alt data-holder-rendered="true">{{ question.question }}</h3>
<div class="card-body container">
<div class="card-text form-check">
<input
class="form-check-input"
type="radio"
name="gridRadios"
id="a1"
:value="question.answer1"
v-model="answer"
/>
<h4 class="font-weight-light" for="a1">{{ question.answer1 }}</h4>
</div>
<div class="card-text form-check">
<input
class="form-check-input"
type="radio"
name="gridRadios"
id="a2"
:value="question.answer2"
v-model="answer"
/>
<h4 class="font-weight-light" for="a2">{{ question.answer2 }}</h4>
</div>
<div class="card-text form-check">
<input
class="form-check-input"
type="radio"
name="gridRadios"
id="a3"
:value="question.answer3"
v-model="answer"
/>
<h4 class="font-weight-light" for="a3">{{ question.answer3 }}</h4>
</div>
</div>
<div class="card-text container">
<small class="text-muted">{{ question.user }}</small>
<button
href="#"
class="btn btn-primary my-3 mx-10 btn float-right shadow-sm rounded"
#click.prevent="answerQuestion"
>Save</button>
</div>
</div>
</form>
</div>
</div>
</div>
And the script:
export default {
name: "questions",
data() {
return {
q: null,
answer: null
};
},
}
As you can see, at the beginning of the form, I tried to get the question element using v-model in a "fake" input, but it gives me an error that it's conflicted with the v-bind of the value (the question) I want to grab. Of course, I can't use v-model on the headline itself because Vue allows to use it only on inputs.
I've tried to change the v-model into v-model="questionCollection[index].question, but then I have no idea how to get it in the script and, let's say, log it to the console with the corresponding answer.

One way to handle this is to submit the question and answer together in the Save button's click-handler. That is, change answerQuestion() to receive question (the iterator variable in v-for) and answer, and update the Save button's click handler in the template to pass those two variables:
// template
<div v-for="(question, index) in questionCollection">
...
<button #click.prevent="answerQuestion(question, answer)">Save</button>
</div>
// script
answerQuestion(question, answer) {
console.log({ question, answer })
}
demo

Related

How to use Vuetify for a Keycloak Login theme

I'm still quite new to web development, so there might be some very basic misunderstandings on my part.
What I'm trying to do:
We use Keycloak to manage access to a web app. The app is Vue project using Vuetify. To achieve a unified look, I want to use Vuetify to design the Keycloak theme for the login as well.
Here is the screendesign:
What I've done so far:
I created the folder keycloak/themes/my-theme, and added these files:
I took the two ftl files from the base theme, then I tried to rewrite the login with Vuetify classes.
To do so I inspected the code of the elements I want in the Vuetify docs and worked them into login.ftl. While this did work more or less for the Log In button, it seems to be completely wrong for the inputs.
Here is what it looks like:
The misalignment is probably fixable with a lot of fine tuning, but one thing I don't know what to do about is that the text inputs don't get activated when I click into them.
I'm pretty sure that copying code from the developer tools is the wrong way to do this. Is there any way to actually design a Keycloak theme with Vuetify? Without basically copying the Vuetify code?
Here is the current login.ftl:
<#import "template.ftl" as layout>
<#layout.registrationLayout displayInfo=social.displayInfo displayWide=(realm.password && social.providers??); section>
<#if section = "header">
${msg("loginWelcomeMessage")}
<#elseif section = "form">
<div id="kc-form" <#if realm.password && social.providers??>class="${properties.kcContentWrapperClass!}"</#if>>
<div id="kc-form-wrapper" <#if realm.password && social.providers??>class="${properties.kcFormSocialAccountContentClass!} ${properties.kcFormSocialAccountClass!}"</#if>>
<#if realm.password>
<form id="kc-form-login" onsubmit="login.disabled = true; return true;" action="${url.loginAction}" method="post">
<div class="${properties.kcFormGroupClass!}">
<div class="v-input rom__text--darkblue theme--light v-text-field v-text-field--is-booted v-text-field--enclosed v-text-field--outlined v-text-field--placeholder">
<div class="v-input__control">
<div class="v-input__slot">
<div class="v-input__prepend-inner">
<div class="v-input__icon v-input__icon--prepend-inner"><i aria-hidden="true" class="v-icon notranslate fas fa-user-circle theme--light"></i></div>
</div>
<fieldset aria-hidden="true">
<legend style="width: 188.25px;"><span>​</span></legend>
</fieldset>
<div class="v-text-field__slot">
<label for="username" class="v-label v-label--active" style="left: -28px; right: auto; position: absolute;">
${msg("usernameOrEmail")}
</label>
<#if usernameEditDisabled??>
<input tabindex="1" id="username" placeholder="Ihr Nutzername" type="text" value="${(login.username!'')}" type="text" disabled>
<#else>
<input tabindex="1" id="username" placeholder="Ihr Nutzername" type="text">
</#if>
</div>
</div>
<div class="v-text-field__details">
<div class="v-messages theme--light">
<div class="v-messages__wrapper"></div>
</div>
</div>
</div>
</div>
</div>
<div class="${properties.kcFormGroupClass!}">
<label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label>
<input tabindex="2" id="password" class="${properties.kcInputClass!}" name="password" type="password" autocomplete="off" />
</div>
<div class="${properties.kcFormGroupClass!} ${properties.kcFormSettingClass!}">
<div id="kc-form-options">
<#if realm.rememberMe && !usernameEditDisabled??>
<div class="checkbox">
<label>
<#if login.rememberMe??>
<input tabindex="3" id="rememberMe" name="rememberMe" type="checkbox" checked> ${msg("rememberMe")}
<#else>
<input tabindex="3" id="rememberMe" name="rememberMe" type="checkbox"> ${msg("rememberMe")}
</#if>
</label>
</div>
</#if>
</div>
<div class="${properties.kcFormOptionsWrapperClass!}">
<#if realm.resetPasswordAllowed>
<span><a tabindex="5" href="${url.loginResetCredentialsUrl}">${msg("doForgotPassword")}</a></span>
</#if>
</div>
</div>
<div id="kc-form-buttons" class="${properties.kcFormGroupClass!} align-center justify-center">
<input type="hidden" id="id-hidden-input" name="credentialId" <#if auth.selectedCredential?has_content>value="${auth.selectedCredential}"</#if>/>
<button tabindex="4" name="login" id="kc-login" type="submit" class=" align-center justify-center v-btn rom__darkblue rom__text--white elevation-2 v-size--large">
<i class="v-icon v-icon--left fas fa-sign-in-alt"></i>
${msg("doLogIn")}
</button>
</div>
</form>
</#if>
</div>
<#if realm.password && social.providers??>
<div id="kc-social-providers" class="${properties.kcFormSocialAccountContentClass!} ${properties.kcFormSocialAccountClass!}">
<ul class="${properties.kcFormSocialAccountListClass!} <#if social.providers?size gt 4>${properties.kcFormSocialAccountDoubleListClass!}</#if>">
<#list social.providers as p>
<li class="${properties.kcFormSocialAccountListLinkClass!}"> <span>${p.displayName}</span></li>
</#list>
</ul>
</div>
</#if>
</div>
<#elseif section = "info" >
<#if realm.password && realm.registrationAllowed && !registrationDisabled??>
<div id="kc-registration">
<span>${msg("noAccount")} <a tabindex="6" href="${url.registrationUrl}">${msg("doRegister")}</a></span>
</div>
</#if>
</#if>
</#layout.registrationLayout>
It looks like you are using Vuetify styles only. I would try to use Vue with the top element:
new Vue({
el: "#kc-form",
vuetify: new Vuetify(),
...
And then I would create all inputs with Vuetify, e.g. username:
<v-text-field label="Username or email" id="username" />
But you need to mimic those original HTML inputs, so you need to add also some parameters, e.g. name - I would use mounted for that - naive implementation:
mounted() {
document.getElementById("username").setAttribute("name", "username")
}
It may need also changes in the layout template and more tweaks to have naive Vuetify feel and full Keycloak functionality. It will be nice if you share your results (even incomplete) on the GitHub.
Proof of concept: https://github.com/jangaraj/vuetify-keycloak-theme/ :

Vuejs: How to reset Input boxes inside Vue Component

Whenever I open the component, the previous information that I've typed still remains in the Input Boxes. I have three input box. One is not inside a form tag and the other two are inside a form tag. How do we clear the Input Boxes when I open the component? I tried using this.form.reset(); but it does not work.
<!-- left column -->
<div class="col">
<div class="form-group">
<label>Scan Item</label>
<input type="search" class="form-control is-warning" id="exampleSearchItemBarcode" placeholder="Item Barcode"
#keyup.enter="searchloanItem"
v-model="itemvmodel"
autocomplete="off">
</div>
</div>
</div>
<!-- right column -->
<div class="col">
<div class="card-body">
<form #submit.prevent="createLoan">
<div class="form-group" v-for="loanpatron in loanpatrons" :key="loanpatron.id">
<input type="text" name="patron_id"
v-model="form.patron_id"
class="form-control text-uppercase"
:class="{ 'is-invalid': form.errors.has('patron_id') }"
autocomplete="off">
<has-error :form="form" field="patron_id"></has-error>
</div>
<!-- textarea -->
<div class="form-group">
<label>Date Loaned</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-calendar-alt"></i></span>
</div>
<input v-model="form.dateloaned" type="date" name="dateloaned" class="form-control" :class="{ 'is-invalid': form.errors.has('dateloaned') }"
data-inputmask-alias="datetime" data-inputmask-inputformat="dd/mm/yyyy" data-mask="" im-insert="false">
</div>
<has-error :form="form" field="dateloaned"></has-error>
</div>
<div class="row">
<div class="col">
<div class="modal-footer">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</div>
</form>
</div>
</div>
<script>
export default {
mounted() {
this.form.reset();
}
}
</script>

angular 5, Keeping value on inputs and adding it to JS object

I am making a web tool in angular 5 to help in the creation of new XML.
I am having trouble passing around the values of inputs and keeping them in the same input at the same time.
I Have a form with an event of ngSubmit that uses as a paremeter the reference of an ngForm
<form class="form-group" (ngSubmit)="createSimpleTypeList(f)" #f="ngForm" >
<div class="container">
<div class="row">
<div class="col-sm-12" >
<button class="btn btn-danger flex ">Construir Json!</button>
</div>
</div>
</div>
<hr>
<div class="container" *ngFor="let type of objSimpleType;let i =index">
<hr>
<div class="row">
<div class="col-sm-3">
<input type="text" [(ngModel)]="type.name" name="nameType"/>
<br>
Inserte el nombre del tipo:
</div>
<div class="col-sm-3">
<input type="text" [(ngModel)]="type.type" name="type"/>
<br>
Inserte el tipo:
</div>
<div class="col-sm-2">
<input type="number" [(ngModel)]="type.min" name="min"/>
<br>
Inserte minimo:
</div>
<div class="col-sm-2">
<input type="number" [(ngModel)]="type.max" name="max"/>
<br>
Inserte maximo:
</div>
<div class="col-sm-2">
<input type="checkbox" name="number" /> Numero?
</div>
</div>
</div>
<hr>
</form>
This is the createSimpleTypeList method
console.log(form.value.nameType);
this.objSimpleType.push({
'-name': form.value.nameType,
'xsd:restriction': {
'-base': form.value.type,
'xsd:minLength': {
'-value': form.value.min
},
'xsd:maxLenght': {
'-value': form.value.max
}
}
});
this.typeList.emit(form.value.nameType);
this.sharedNameSpace.emit(this.namespace);
The problem I am having is that I have not found a way to push new items into objSimpleType, without erasing the value of inputs, and if I manage to not erase the value of the inputs then the values of the form wont push.
this is how it looks.how the inputs work right now
This is what I need
How the inputs should be
I am still new to angular, and I would really appreciate the help.
Now that I have looked found the solution, I had two mistakes in the code above.
First my logic was not well executed, I needed to work with unique IDs,and call them dynamically.
Second, I was having trouble to call them , and after reading a lot, I found that I can call the parameters of a Json with brackets[].
"form.value['nameType' + this.i]" --Something like this is what i Needed.

Vue v-model data binding issue

I have a pair of standard Bootstrap radio buttons and I wanted v-model to set a value in the vue data object when one is selected.
This works just fine in a JSfiddle, however this doesn't work when running locally on my own environment.
I can get other directives working fine like 'v-if', but v-model just doesn't seem to work at all.
Nothing comes up in the console, and when using vue dev tools extension; I can see that the 'test' object is never updated when one of the radio buttons is selected.
I'm using the latest version of bootstrap, jquery and the vue development version.
<div class="container margin-top-rem" id="app">
<div class="row">
<div class="col-12">
<div class="card text-center" v-if="testIf">
<div class="tab-content" id="nav-tabContent">
<div class="tab-pane fade show active card-body-padding">
<div class="row">
<div class="col-12">
<div class="form-group">
<div class="btn-group btn-group-toggle" data-toggle="buttons">
<label class="btn btn-secondary">
<input type="radio" name="options" autocomplete="off" v-model="testVModel" :value="false" />false
</label>
<label class="btn btn-secondary">
<input type="radio" name="options" autocomplete="off" v-model="testVModel" :value="true" />true
</label>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div>test value is {{testVModel}}</div>
</div>
</div>
</div>
var app = new Vue({
el: '#app',
data: {
testIf: 'blah',
testVModel: ''
}
})
It looks like this is a conflict between bootstrap and Vue.
Two options to resolve are:
1) Use Bootstrap-vue
2) Remove 'data-toggle="buttons"' from:
<div class="btn-group-toggle" data-toggle="buttons">

Using vue js with selectpicker

I'm using Vue.js to add multiple rows, each row contain two datetimepicker inputs and a bootstrap-select.
My problem is when I fill the inputs and click to add a new row the previous ones clear out, the reason is each time I add a new row I'm using setTimeout to referesh the selectpicker and the datetimepicker.
So I want to know if there is a way to trigger the last added element them without refreshing the previous ones.
Here is my code :
HTML
<div class="cols" id="app">
<ul style="list-style-type: none;">
<li>
<button style="float: right;margin-right: 20px;margin-top: 5px;" type="button" class="btn btn-success btn-xs" v-on:click="addRow()">
<i class="fa fa-plus"></i> Ajouter
</button>
</li>
<li v-for="(row, id) in rows">
<div class="form-inline cp-out" style="padding-bottom: 9px;border-radius:4px;background-color:white;margin-left:-20px;display:inline-block;width:100%;margin-top:10px;">
<div class="col-md-3">
<label :for="['time_start'+id]" class="control-label">start time</label>
<div class="input-group date form_time" style="width:100%;" data-date="" data-date-format="hh:ii" :data-link-field="['time_start'+id]" data-link-format="hh:ii">
<input v-model="row.startTime" :name="'rows['+id+'][startTime]'" class="form-control" :id="['startTime' + id]" size="16" type="text" value="" >
<span class="input-group-addon"><span class="glyphicon glyphicon-time"></span></span>
</div>
</div>
<div class="col-md-3">
<label :for="['time_end'+id]" class="control-label" style="margin-left:7px;">end time</label>
<div class="input-group date form_time" style="width:100%;" data-date="" data-date-format="hh:ii" :data-link-field="['time_end'+id]" data-link-format="hh:ii">
<input v-model="row.endTime" :name="'rows['+id+'][endTime]'" class="form-control" :id="['endTime'+id]" size="16" type="text" value="" >
<span class="input-group-addon"><span class="glyphicon glyphicon-time"></span></span>
</div>
</div>
<div class="col-md-4">
<div class="form-group select_group" style="margin-left:5px;">
<label :for="['status' + id]" class="control-label">Status</label>
<select data-width="100%" v-model="row.status" :name="'rows['+id+'][status]'" :id="['status' + id]" class="select_picker" data-live-search="true" multiple >
<option value="Status 1">Status 1</option>
<option value="Status 2">Status 2</option>
</select>
</div>
</div>
<div class="col-xs-1">
<button type="button" class="btn btn_delete btn-xs btn-danger" v-on:click="delRow(id)">
<i class="fa fa-remove"></i> delete
</button>
</div>
</div>
</li>
</ul>
</div>
JS
app = new Vue({
el: '#app',
data: {
rows: []
},
methods: {
addRow: function () {
this.rows.push({startTime: '', endTime: '', status: []});
setTimeout(function () {
$('.select_picker').selectpicker('refresh');
$('.form_time').datetimepicker({format: 'LT'});
}.bind(this), 10);
},
delRow: function (id) {
this.rows.splice(id, 1);
}
},created:function() {
this.addRow();
}
});
This is the example : https://jsfiddle.net/rypLz1qg/9/
You really need to write a wrapper component. Vue expects to control the DOM and not to have you making DOM-changing things like the *picker calls at arbitrary times. However, a component gives you the opportunity to tell your component how to interact with the DOM in each of its lifecycle hooks so that its behavior can be consistent. See the JavaScript tab on the wrapper component example page.