I'm trying to use and understand the Aurelia ValueConverter in the context of a multi-select form. What I thought would be straight forward, has turned out to be a challenge for me.
I have a form to create a new deal which has multiple categories assigned to it via a multi-select input field. I've bound the output from the form into new_deal.categorizations (in the database deals have categories through categorizations).
Right now on create, through a 'brute force' method, I'm converting each category ID into a {category_id: id} object before posting to the API.
Example just logging the POST output:
create(){
var categorizations = this.new_deal.categorizations;
this.new_deal.categorizations = categorizations.map(function (e) {
return {category_id: e}
});
logger.debug ('POST: ', JSON.stringify(this.new_deal));
}
Example output:
POST: {"name":"new deal","categorizations":[{"category_id":"1"},{"category_id":"2"}]}
But I think this would better be accomplished through a ValueConverter.
Plunker is here with the full code but it's basically:
app.js:
export class App {
constructor(){
this.categories = [{id: 1, name: 'test1'}, {id: 2, name: 'test2'}];
this.new_deal = {
name: 'new deal',
categorizations: null,
};
}
create(){
var categorizations = this.new_deal.categorizations;
this.new_deal.categorizations = categorizations.map(function (e) {return {category_id: e}});
logger.debug ('POST: ', JSON.stringify(this.new_deal));
}
create2(){
logger.debug ('POST: ', JSON.stringify(this.new_deal));
}
}
export class CategoryToIDValueConverter {
fromView(id) {
return id ? id: null;
}
}
And app.html:
<template>
<h1>Testing ValueConverter</h1>
<h3 >New Brute Force Deal</h3>
<form role="form">
<label>Name</label>
<input type="text" placeholder="Ex. Buy One Get One Free" value.bind="new_deal.name">
<label>Categories</label>
<select value.bind="new_deal.categorizations" multiple size="2">
<option repeat.for="category of categories" value.bind="category.id">${category.name}</option>
</select>
</form>
<button type="submit" click.delegate="create()">Save</button>
<h3>New ValueConverter Deal</h3>
<form role="form">
<label>Name</label>
<input type="text" placeholder="Ex. Buy One Get One Free" value.bind="new_deal.name">
<label>Categories</label>
<select class="form-control" value.bind="new_deal.categorizations | categoryToID" multiple size="2">
<option repeat.for="category of categories" value.bind="category.id">${category.name}</option>
</select>
</form>
<button class="btn btn-success" type="submit" click.delegate="create2()">Save</button>
</template>
With this I get an output of
POST: {"name":"new deal","categorizations":["1","2"]}
In fromView in app.js, I would think I could change:
return id ? id: null;
To return an object instead of an individual value:
return id ? {category_id: id} : null
But that results in this error:
Uncaught Error: Only null or Array instances can be bound to a multi-select.
Upon further inspection, it looks like id is coming into fromView as an array...
So I modified fromView to this:
fromView(id) {
if(id){
var categorizations = [];
id.forEach(function(cat_id){
categorizations.push({category_id: cat_id})
});
logger.debug(categorizations);
logger.debug(Object.prototype.toString.call(categorizations));
return categorizations;
} else { return null; }
}
}
Trying to expect an array, and then build an array of categorization objects to return, but as you can see in this Plunker, it loses the select as you click (though the debug logs show the objects being created).
You have an array of category objects, each having a name (string) and id (number). These will be used to populate a select element that allows multiple selection:
export class App {
categories = [
{ id: 1, name: 'test1'},
{ id: 2, name: 'test2'}
];
}
<select multiple size="2">
<option repeat.for="category of categories">${category.name}</option>
</select>
The deal object is comprised of a name (string) and categorizations. Categorization objects look like this: { category_id: 1 }
export class App {
categories = [
{ id: 1, name: 'test1'},
{ id: 2, name: 'test2'}];
deal = {
name: 'new deal',
categorizations: [],
}
}
We want to bind the select element's value to the deal object's categorizations which is an array of objects. This means each of the select element's options need to have a object "value". An HTMLOptionElement's value attribute only accepts strings. Anything we assign to it will be coerced to a string. We can store the categorization object in a special model attribute which can handle any type. More info on this can be found in the aurelia docs.
<select multiple size="2">
<option repeat.for="category of categories" model.bind="{ category_id: category.id }">${category.name}</option>
</select>
Finally we need to bind the select element's value to the deal object's categorizations:
<select value.bind="deal.categorizations" multiple size="2">
<option repeat.for="category of categories" model.bind="{ category_id: category.id }">${category.name}</option>
</select>
All together, the view and view-model look like this:
export class App {
categories = [
{ id: 1, name: 'test1'},
{ id: 2, name: 'test2'}];
deal = {
name: 'new deal',
categorizations: [],
}
createDeal() {
alert(JSON.stringify(this.deal, null, 2));
}
}
<template>
<form submit.delegate="createDeal()">
<label>Name</label>
<input type="text" placeholder="Ex. Buy One Get One Free" value.bind="deal.name">
<label>Categories</label>
<select value.bind="deal.categorizations" multiple size="2">
<option repeat.for="category of categories" model.bind="{ category_id: category.id }">${category.name}</option>
</select>
<button type="submit">Save</button>
</form>
</template>
Here's a working plunker: http://plnkr.co/edit/KO3iFBostdThrHUA0QHY?p=preview
Figured it out with some help from whayes on the Aurelia Gitter channel. So I was on the right track with expecting an array in the fromView method but I also needed a toView method in the ValueConverter.
export class CategoryToIDValueConverter {
toView(cats){
if (cats){
var ids = [];
cats.forEach(function(categorization){
ids.push(categorization.category_id);
});
return ids;
} else { return null; }
}
fromView(id) {
if(id){
var categorizations = [];
id.forEach(function(cat_id){
categorizations.push({category_id: cat_id})
});
return categorizations;
} else { return null; }
}
}
I had tried that too, but I initially assumed I needed to add the converter to the select line of the form and the option line, like so:
<select class="form-control" value.bind="new_deal.categorizations | categoryToID" multiple size="2">
<option repeat.for="category of categories" value.bind="category.id">${category.name}</option>
</select>
But that's actually incorrect. I only needed to apply the categoryToID ValueConverter to the select line and it all worked as expected.
Working Plunker showing how the brute force method doesn't change the model until you click save, and the ValueConverter changes it any time you change the selection.
Final app.js
import {LogManager} from 'aurelia-framework';
let logger = LogManager.getLogger('testItems');
export class App {
constructor(){
this.categories = [{id: 1, name: 'test1'}, {id: 2, name: 'test2'}];
this.new_deal = {
name: 'new deal',
categorizations: [],
};
setInterval(() => this.debug = JSON.stringify(this.new_deal, null, 2), 100);
}
create(){
var categorizations = this.new_deal.categorizations;
this.new_deal.categorizations = categorizations.map(function (e) {return {category_id: e}});
alert(JSON.stringify(this.new_deal, null, 2));
}
create2(){
alert(JSON.stringify(this.new_deal, null, 2));
}
}
export class CategoryToIDValueConverter {
toView(cats){
if (cats){
var ids = [];
cats.forEach(function(categorization){
ids.push(categorization.category_id);
});
return ids;
} else { return null; }
}
fromView(id) {
if(id){
var categorizations = [];
id.forEach(function(cat_id){
categorizations.push({category_id: cat_id})
});
return categorizations;
} else { return null; }
}
}
Final app.html
<template>
<h1>Testing ValueConverter</h1>
<h3 >New Brute Force Deal</h3>
<form role="form">
<label>Name</label>
<input type="text" placeholder="Ex. Buy One Get One Free" value.bind="new_deal.name">
<label>Categories</label>
<select value.bind="new_deal.categorizations" multiple size="2">
<option repeat.for="category of categories" value.bind="category.id">${category.name}</option>
</select>
</form>
<button type="submit" click.delegate="create()">Save</button>
<h3>New ValueConverter Deal</h3>
<form role="form">
<label>Name</label>
<input type="text" placeholder="Ex. Buy One Get One Free" value.bind="new_deal.name">
<label>Categories</label>
<select class="form-control" value.bind="new_deal.categorizations | categoryToID" multiple size="2">
<option repeat.for="category of categories" value.bind="category.id">${category.name}</option>
</select>
</form>
<button type="submit" click.delegate="create2()">Save</button>
<!-- debug -->
<pre><code>${debug}</code></pre>
</template>
Related
I have the following select dropdown in Vue.js -2
<p-grid-item v-if="isOption">
<div class="form-field required selectbox">
<span class="region-dd-icon icon-sf-dropdown"></span>
<label class="move-label">{{ $t("region") }}</label>
<select name="region" id="region" label="region" v-validate="'required'" v-model="region" class="input required selectbox region" data-vv-scope="userDetails" aria-label="Region" #change="regionChange()">
<option value="">{{ $t("region") }}</option>
<p-option v-for="regionOption in regionList" :key="regionOption.region" :value="regionOption.plane">{{$t(regionOption.region)}}</p-option>
</select>
</div>
<p class="small-txt-info" v-html="$t('regionInfo')"></p>
</p-grid-item>
I am getting the regionList from the configuration file and using the following code for it.
In the configuration file I have :
regions :[
{'region': 'US', plane: 'US_STAGING'},
{'region': 'EU', plane: 'EU_STAGING'}
]
and in the component
mounted () {
this.isOption = this.$route.query.plan === 'myPlan'
if (this.isOption) {
this.regionList = config[process.env.NODE_ENV].regions
this.$store.dispatch('updateRegionList', this.regionList)
}
}
In the store.js file, I have
const store = new Vuex.Store({
state: initState(),
mutations: {
setRegionList: function (state, regionList) {
state.regionList = regionList
}
},
actions: {
updateRegionList: function (context, regionList) {
context.commit('setRegionList', regionList)
}
}
}
Now I am trying to make the first value of the select as selected by default and so trying to set value for region as it is mentioned as v-model. I am using the following code :
created () {
this.region = this.regionList[0]
}
[__ob__: Observer]
length
:
0
__ob__
:
Observer {value: Array(0), dep: Dep, vmCount: 0}
[[Prototype]]
:
Array
How can I set the default value for my select box ? I am new to Vue and so any help will be appreciated.
I have a svelte component where i want to connect a selected input with a declared attribute.
My problem is that the binding of the selected value of status to the attribute'status' declared in 'flightschedules' doesnt work.
The options are from the attribute questions: on-time, delayed, cancelled
Can somebody help me please ?
Here is my code (its a component to create form, e.g create a flightschedule):
<script>
import axios from "axios";
import { onMount } from "svelte";
export let params = {};
let flightschedule = {
timeofdeparture: "",
flightnumber: "",
gatenumber: "",
status: "",
privatejetline_id: null,
};
let questions = [
{ text: "on-time" },
{ text: "delayed" },
{ text: "cancelled" },
];
let selected;
let privatejetline_ids = [];
onMount(() => {
getPrivateJetLineIds();
selected = params.status;
});
function getPrivateJetLineIds() {
axios
.get("http://localhost:8080/flights/privatejetline")
.then((response) => {
privatejetline_ids = [];
for (let privatejetline of response.data) {
privatejetline_ids.push(privatejetline.id);
}
flightschedule.privatejetline_id = privatejetline_ids[0];
});
}
function addFlightSchedule() {
axios
.post("http://localhost:8080/flights/flightschedule", flightschedule)
.then((response) => {
alert("Flight Schedule added");
console.log(response.data);
})
.catch((error) => {
console.log(error);
alert(error);
});
}
</script>
<div class="mb-3">
<label for="" class="form-label">Status</label>
<select bind:value={flightschedule.status} class="from-select">
<option value="" disabled>-- Select Status --</option>
{#each questions as question}
<option value={selected} selected={selected===flightschedule.status}>{question.text}</option>
{/each}
</select>
</div>
Actually, no need for selected variable, just bind the flightschedule.status. Try following in REPL.
<script>
let flightschedule = {
timeofdeparture: "",
flightnumber: "",
gatenumber: "",
status: "",
privatejetline_id: null,
};
let questions = [
{ text: "on-time" },
{ text: "delayed" },
{ text: "cancelled" },
];
$: console.log('---->', flightschedule.status)
</script>
<div class="mb-3">
<label for="" class="form-label">Status</label>
<select bind:value={flightschedule.status} class="from-select">
<option value="" disabled>-- Select Status --</option>
{#each questions as question}
<option value={question.text}>{question.text}</option>
{/each}
</select>
</div>
<option value={selected} this line can’t be right. You’re binding all three options to the same value.
You probably want following:
<select bind:value={selected} class="from-select">
<option value="" disabled>-- Select Status --</option>
{#each questions as question}
<option value={question.text}>{question.text}</option>
{/each}
</select>
I'm looking solution for how to auto fill input in vue js. I have a form which include of input type text, select dropdown, quantity, etc. I want when the select dropdown is selected, then the quantity of vCPU, vRAM, and Storage Capacity will be automatically filled with the value according to the selected Server Flavor.
I tried to choose Flavor Server with Flavor 1 options, vCPU should immediately be filled with a value 4, vRAM should be filled with a value 2, and storage capacity should be filled with a value 10. But the quantity does not appear.
But in the price estimation the numbers are correct, namely vCPU (4), vRAM (2), Storage Capacity (10)
I'm confused, to put the if conditional in the <base-quantity> at the #updateQuantity custom event or in v-if attribute. Is there anyone here that can help me solve this problem?
The full source code is in this codesandbox => https://codesandbox.io/s/suspicious-almeida-rjyy9
Lite.vue
<template>
<div class="container">
<h2 class="font-size-26 txt-secondary">Deka Flexi</h2>
<div class="row">
<div class="col-12">
<form #submit.prevent="submitFormLite">
<div class="form-group form-group--border">
<label class="font-size-22 txt-secondary txt-semibold">Commitment Type</label>
<select name="commitment" id="" class="select-custom" v-model="selectedCommitTypeLite">
<option v-for="ctl in commitTypeLite" :key="ctl.name" :value="ctl.value">{{ ctl.name }}</option>
</select>
</div>
<div class="form-group form-group--border">
<label class="font-size-22 txt-secondary txt-semibold">Server Name</label>
<input type="text" name="name" class="custom-input" v-model="serverNameLite" />
</div>
<div class="form-group form-group--border">
<label class="font-size-22 txt-secondary txt-semibold">Server Flavor</label>
<select name="storage-type" id="" class="select-custom" v-model="selectedServerFlavorLite">
<option v-for="sfl in serverFlavorLite" :key="sfl.name" :value="sfl.value">{{ sfl.name }}</option>
</select>
</div>
<h6 class="font-size-22 txt-secondary txt-semibold">
Components
</h6>
<div class="row form-group--border">
<div class="col-md-6">
<div class="form-group text-center">
<div class="font-size-18 txt-secondary">vCPU (GHz)</div>
<base-quantity #updateQuantity="updateCpuLite"></base-quantity>
</div>
</div>
<div class="col-md-6">
<div class="form-group text-center">
<div class="font-size-18 txt-secondary">vRAM (GB)</div>
<base-quantity #updateQuantity="updateRamLite"></base-quantity>
</div>
</div>
<div class="col-md-6">
<div class="form-group text-center">
<label class="font-size-18 txt-secondary">Storage Type</label>
<select name="storage-type" id="" class="select-custom" v-model="selectedStorageTypeLite">
<option v-for="stl in storageTypeLite" :key="stl.name" :value="stl.value">{{ stl.name }}</option>
</select>
</div>
</div>
<div class="col-md-6">
<div class="form-group text-center">
<div class="font-size-18 txt-secondary">Storage Capacity (GB)</div>
<base-quantity #updateQuantity="updateCapacityLite" v-model="updateCapacityLite"></base-quantity>
</div>
</div>
</div>
<div class="row pt-4">
<div class="col-md-6">
<div class="form-group text-center">
<label class="font-size-18 txt-secondary">Public IP</label>
<select name="public-ip" id="" class="select-custom" v-model="selectedPublicIpLite">
<option v-for="pil in publicIpLite" :key="pil.name" :value="pil.value">{{ pil.name }}</option>
</select>
</div>
</div>
<div class="col-md-6">
<div class="form-group text-center">
<label class="font-size-18 txt-secondary">OS Type</label>
<select name="os-lite" id="" class="select-custom" v-model="selectedOsLite">
<option v-for="ol in osLite" :key="ol.name" :value="ol.value">{{ ol.name }}</option>
</select>
</div>
</div>
<div class="col-md-6">
<div class="form-group text-center">
<div class="font-size-18 txt-secondary">Quantity</div>
<base-quantity #updateQuantity="updateQuantityLite"></base-quantity>
</div>
</div>
</div>
<div class="form-group mt-4 text-center">
<button class="button button__add" #click="addToCart">Submit</button>
</div>
</form>
</div>
</div>
</div>
</template>
<script>
import BaseQuantity from "../base/BaseQuantity.vue";
export default {
components: {
BaseQuantity,
},
data() {
return {
serverNameLite: '',
storageTypeLite: [
{
name: "Storage Type 1",
value: 100
},
{
name: "Storage Type 2",
value: 120
}
],
publicIpLite: [
{
name: "Yes",
value: 120
},
{
name: "No",
value: 20
}
],
osLite: [
{
name: "OS 1",
value: 80
},
{
name: "OS 2",
value: 100
},
{
name: "OS 3",
value: 120
}
],
serverFlavorLite: [
{
name: "Flavor 1",
value: "flavor-1"
},
{
name: "Flavor 2",
value: "flavor-2"
},
{
name: "Flavor 3",
value: "flavor-3"
}
],
commitTypeLite: [
{
name: "Commitment Type 1",
value: 80
},
{
name: "Commitment Type 2",
value: 100
},
{
name: "Commitment Type 3",
value: 120
}
],
selectedStorageTypeLite: "",
selectedPublicIpLite: "",
selectedOsLite: "",
selectedCommitTypeLite: "",
selectedServerFlavorLite:""
};
},
watch: {
serverNameLite: function() {
this.$store.commit('setServerNameLite', this.serverNameLite);
},
selectedStorageTypeLite: function() {
let storageTypeLite = this.storageTypeLite.find((storageTypeLite) => storageTypeLite.value == this.selectedStorageTypeLite);
this.$store.commit('setStorageTypeLite', storageTypeLite);
},
selectedPublicIpLite: function() {
let publicIpLite = this.publicIpLite.find((publicIpLite) => publicIpLite.value == this.selectedPublicIpLite);
this.$store.commit('setPublicIpLite', publicIpLite);
console.log(publicIpLite);
},
selectedOsLite: function() {
let osLite = this.osLite.find((osLite) => osLite.value == this.selectedOsLite);
this.$store.commit('setOsLite', osLite);
},
selectedCommitTypeLite: function() {
let commitTypeLite = this.commitTypeLite.find((commitTypeLite) => commitTypeLite.value == this.selectedCommitTypeLite);
this.$store.commit('setCommitTypeLite', commitTypeLite);
},
selectedServerFlavorLite: function() {
let serverFlavorLite = this.serverFlavorLite.find((serverFlavorLite) => serverFlavorLite.value == this.selectedServerFlavorLite);
this.$store.commit('setServerFlavorLite', serverFlavorLite);
if(this.selectedServerFlavorLite == "flavor-1"){
this.updateCpuLite(4);
this.updateRamLite(2);
this.updateCapacityLite(10);
}
},
},
methods: {
async addToCart() {
let isLiteEmpty = await this.$store.dispatch('isLiteEmpty');
if(!isLiteEmpty) {
this.$store.commit('calculateLiteCost');
this.$store.commit('setLite', this.$store.getters.getLiteState);
this.$store.commit('calculatePrice');
}
},
updateCpuLite(val) {
this.$store.commit('setCpuLite', {qty: val, value: 100});
console.log(val);
},
updateRamLite(val) {
this.$store.commit('setRamLite', {qty: val, value: 100});
console.log(val);
},
updateCapacityLite(val) {
this.$store.commit('setCapacityLite', {qty: val, value: 100});
console.log(val);
},
updateQuantityLite(val) {
this.$store.commit('setQuantityLite', {qty: val, value: 100});
console.log(val);
},
},
};
</script>
selectedServerFlavorLite: function() {
let serverFlavorLite = this.serverFlavorLite.find((serverFlavorLite) => serverFlavorLite.value == this.selectedServerFlavorLite);
this.$store.commit('setServerFlavorLite', serverFlavorLite);
if(this.selectedServerFlavorLite == "flavor-1"){
this.updateCpuLite(4);
this.updateRamLite(2);
this.updateCapacityLite(10);
}
},
BaseQuantity.vue
<template>
<div class="quantity" :class="disabled ? 'quantity__untoggle' : 'quantity__toggle'">
<button type="button" #click="decrement" class="btn quantity__decrement" :disabled="disabled">-</button>
<input type="text" class="quantity__value" :value="quantity" :disabled="disabled" readonly>
<button type="button" #click="increment" class="btn quantity__increment" :disabled="disabled">+</button>
</div>
</template>
<script>
export default {
props : ['disabled'],
data(){
return{
quantity: null
}
},
watch:{
quantity :function(val){
this.$emit('updateQuantity',val);
}
},
methods :{
increment () {
this.quantity++
},
decrement () {
if(this.quantity === 0) {
alert('Negative quantity not allowed')
} else {
this.quantity--
}
}
}
}
</script>
There are multiple ways, but it's all based on how your data is stored and connected through components.
Let's start from BaseQuantity.vue:
data() {
return {
quantity: 0 // you have a local saved state of the quantity
}
},
methods: {
increment() {
this.quantity++ // when user hits something, you increment the LOCAL state
},
}
watch: {
quantity: function(val) { // when the LOCAL value updates
this.$emit('updateQuantity', val); // you emit event it has been updated
}
}
Basically each of your Base Quantity components defines its state (starting from 0), and then tracks its own actions that update that state.
You use those components like
<base-quantity #updateQuantity="updateServer"
And the method calls Vuex to store the new value (gotten from the component, equals to it's internal state):
updateServer(val) {
this.$store.commit('setServer', {qty: val, value: 100});
}
Your first issue is that each of those Base Quantity components define their own initial state, which is internal and separate. Currently, there's no real way to tell any of those "your value is X", it's quite the opposite - they tell the parent "I've updated my value".
In order to do so, you must somehow set the initial value. The very basic approach would be to pass the initial value to the component:
props : ['disabled', 'initialValue'],
data(){
return {
quantity: this.initialValue
}
},
Problem two now is that you don't only need an initial value, you need to set the value from outside, whenever the user selects a dropdown option. But you would also like to keep track of manual updates on the component. So you need a two-directional binding of the values (set and update). That's where the v-model comes in handy. Here's a good article explaining how it worked before, and how it works now: https://v3-migration.vuejs.org/breaking-changes/v-model.html#overview Basically you'll use it like that:
<base-quantity v-model="serverQuantity" />
<!-- would be shorthand for: -->
<base-quantity
:modelValue="serverQuantity"
#update:modelValue="serverQuantity= $event"
/>
You don't store the data in your Calculator component - you store it in Vuex. Now this is the part where you have a lot of solutions and you need to be careful how you design your data flow. I would go with the simplest one:
Use store getters to instruct Base Quantity what its value is: <base-quantity :value="$store.getters.serverQuantity" />. This is a reactive property that would be updated when the store updates its value for server quantity. If you don't have getters you could use the state instead, but that's not suggested.
Remove local variable for quantity, and just use the passed property: <input :value="value" />
Upon update (clicking on a button), emit an event with the new value without updating it locally: increment() { this.$emit('updateQuantity', this.value + 1)
In your handler, commit the update as of now
In order to handle dropdown selection, if you use the approach described above, you just need to wait for the user input (dropdown selection) and populate the store with all the needed fields. Since they are passed to each component, the value would be automatically populated:
watch: {
selectedPackage: function() {
let pack = this.storagePackage.find((pack) => pack.value == this.selectedPackage);
this.$store.commit('setPackage', pack);
// here you should somehow map the pack to the values you'd like to populate
// let's say package "One" (value: 30) means you'd have 8 vRAM, then:
this.updateRam(package.ram);
// this would call this.$store.commit('setRam', { qty: 8, value: 100 });
this.updateCapacity(100);
this.updateCpu(5);
// all the other fields you'd like to update, with the specific values this PACK has (I haven't seen any map for plan -> values)
},
}
p.s. I'm not entirely sure why you store qty: val, value: 100 everywhere, but probably you have some reason about it.
With this approach you'll have a single source of truth about the data. And separate components that just say "I want this value (whatever it is) to be either incremented or decremented". So the component is completely isolated of any knowing of the real business logic of modification and storage, as well as the property name.
The parent is the one that handles data both ways - first it sends it to each component, and second - it handles the user actions, which are then commited to a Vuex store (single point of truth).
You can set values using the v-model but this is a different scenario where the values are based on the selected events. So I reached this solution where you can create a function that sets the values in custom mode
Try using this:
setValues: function () {
this.specifications === this.specifications[1]
? (this.os = this.os[0].name)
: (this.os = '');
You can pass in other Databases, StorageType, Firewall values as well.
The example code in the react-bootstrap site shows the following. I need to drive the options using an array, but I'm having trouble finding examples that will compile.
<Input type="select" label="Multiple Select" multiple>
<option value="select">select (multiple)</option>
<option value="other">...</option>
</Input>
You can start with these two functions. The first will create your select options dynamically based on the props passed to the page. If they are mapped to the state then the select will recreate itself.
createSelectItems() {
let items = [];
for (let i = 0; i <= this.props.maxValue; i++) {
items.push(<option key={i} value={i}>{i}</option>);
//here I will be creating my options dynamically based on
//what props are currently passed to the parent component
}
return items;
}
onDropdownSelected(e) {
console.log("THE VAL", e.target.value);
//here you will see the current selected value of the select input
}
Then you will have this block of code inside render. You will pass a function reference to the onChange prop and everytime onChange is called the selected object will bind with that function automatically. And instead of manually writing your options you will just call the createSelectItems() function which will build and return your options based on some constraints (which can change).
<Input type="select" onChange={this.onDropdownSelected} label="Multiple Select" multiple>
{this.createSelectItems()}
</Input>
My working example
this.countryData = [
{ value: 'USA', name: 'USA' },
{ value: 'CANADA', name: 'CANADA' }
];
<select name="country" value={this.state.data.country}>
{this.countryData.map((e, key) => {
return <option key={key} value={e.value}>{e.name}</option>;
})}
</select>
bind dynamic drop using arrow function.
class BindDropDown extends React.Component {
constructor(props) {
super(props);
this.state = {
values: [
{ name: 'One', id: 1 },
{ name: 'Two', id: 2 },
{ name: 'Three', id: 3 },
{ name: 'four', id: 4 }
]
};
}
render() {
let optionTemplate = this.state.values.map(v => (
<option value={v.id}>{v.name}</option>
));
return (
<label>
Pick your favorite Number:
<select value={this.state.value} onChange={this.handleChange}>
{optionTemplate}
</select>
</label>
);
}
}
ReactDOM.render(
<BindDropDown />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root">
<!-- This element's contents will be replaced with your component. -->
</div>
// on component load, load this list of values
// or we can get this details from api call also
const animalsList = [
{
id: 1,
value: 'Tiger'
}, {
id: 2,
value: 'Lion'
}, {
id: 3,
value: 'Dog'
}, {
id: 4,
value: 'Cat'
}
];
// generage select dropdown option list dynamically
function Options({ options }) {
return (
options.map(option =>
<option key={option.id} value={option.value}>
{option.value}
</option>)
);
}
<select
name="animal"
className="form-control">
<Options options={animalsList} />
</select>
Basically all you need to do, is to map array. This will return a list of <option> elements, which you can place inside form to render.
array.map((element, index) => <option key={index}>{element}</option>)
Complete function component, that renders <option>s from array saved in component's state. Multiple property let's you CTRL-click many elements to select. Remove it, if you want dropdown menu.
import React, { useState } from "react";
const ExampleComponent = () => {
const [options, setOptions] = useState(["option 1", "option 2", "option 3"]);
return (
<form>
<select multiple>
{ options.map((element, index) => <option key={index}>{element}</option>) }
</select>
<button>Add</button>
</form>
);
}
component with multiple select
Working example: https://codesandbox.io/s/blue-moon-rt6k6?file=/src/App.js
A 1 liner would be:
import * as YourTypes from 'Constants/YourTypes';
....
<Input ...>
{Object.keys(YourTypes).map((t,i) => <option key={i} value={t}>{t}</option>)}
</Input>
Assuming you store the list constants in a separate file (and you should, unless they're downloaded from a web service):
# YourTypes.js
export const MY_TYPE_1="My Type 1"
....
You need to add key for mapping otherwise it throws warning because each props should have a unique key. Code revised below:
let optionTemplate = this.state.values.map(
(v, index) => (<option key={index} value={v.id}>{v.name}</option>)
);
You can create dynamic select options by map()
Example code
return (
<select className="form-control"
value={this.state.value}
onChange={event => this.setState({selectedMsgTemplate: event.target.value})}>
{
templates.map(msgTemplate => {
return (
<option key={msgTemplate.id} value={msgTemplate.text}>
Select one...
</option>
)
})
}
</select>
)
</label>
);
I was able to do this using Typeahead. It looks bit lengthy for a simple scenario but I'm posting this as it will be helpful for someone.
First I have created a component so that it is reusable.
interface DynamicSelectProps {
readonly id: string
readonly options: any[]
readonly defaultValue: string | null
readonly disabled: boolean
onSelectItem(item: any): any
children?:React.ReactNode
}
export default function DynamicSelect({id, options, defaultValue, onSelectItem, disabled}: DynamicSelectProps) {
const [selection, setSelection] = useState<any[]>([]);
return <>
<Typeahead
labelKey={option => `${option.key}`}
id={id}
onChange={selected => {
setSelection(selected)
onSelectItem(selected)
}}
options={options}
defaultInputValue={defaultValue || ""}
placeholder="Search"
selected={selection}
disabled={disabled}
/>
</>
}
Callback function
function onSelection(selection: any) {
console.log(selection)
//handle selection
}
Usage
<div className="form-group">
<DynamicSelect
options={array.map(item => <option key={item} value={item}>{item}</option>)}
id="search-typeahead"
defaultValue={<default-value>}
disabled={false}
onSelectItem={onSelection}>
</DynamicSelect>
</div>
I'm using vue js for my application in select option input..I need to set default value should be selected in the drop down and while on change i would like to call two functions ..
I'm new to vue js..
My Code :
var listingVue = new Vue({
el: '#mountain',
data:{
formVariables: {
country_id: '',
mountain_id: '',
peak_id: ''
},
countrylist:[],
mountainlist:[],
},
ready: function() {
var datas = this.formVariables;
this.getCountry();
},
methods: {
getCountry: function()
{
this.$http.get(baseurl+'/api/v1/device/getCountry',function(response)
{
this.$set('countrylist',response.result);
//alert(jQuery('#country_id').val());
});
},
getMountain: function(country_id)
{
var datas = this.formVariables;
datas.$set('country_id', jQuery('#country_id').val() );
postparemeters = {country_id:datas.country_id};
this.$http.post(baseurl+'/api/v1/site/getMountain',postparemeters,function(response)
{
if(response.result)
this.$set('mountainlist',response.result);
else
this.$set('mountainlist','');
});
},
});
<select
class="breadcrumb_mountain_property"
id="country_id"
v-model="formVariables.country_id"
v-on="change:getMountain(formVariables.country_id);">
<option
v-repeat = "country: countrylist"
value="#{{country.id}}" >
#{{country.name}}
</option>
</select>
With vue 2, the provided answer won't work that well. I had the same problem and the vue documentation isn't that clear concerning <select>. The only way I found for <select> tags to work properly, was this (when talking of the question):
<select v-model="formVariables.country_id">
<option v-for = "country in countrylist" :value="country.id" >{{country.name}}</option>
</select>
I assume, that the #-sign in #{{...}} was due to blade, it should not be necessary when not using blade.
In VueJS 2 you can bind selected to the default value you want. For example:
<select
class="breadcrumb_mountain_property"
id="country_id"
v-model="formVariables.country_id"
v-on:change="getMountain(formVariables.country_id);">
<option
v-for = "country in countrylist"
:selected="country.id == 1"
:value="country.id" >
{{country.name}}
</option>
</select>
So, during the iteration of the countryList, the country with the id 1 will be selected because country.id == 1 will be true which means selected="true".
UPDATED:
As Mikee suggested, instead of v-on="change:getMountain(formVariables.country_id);" there is a new way to for binding events. There is also a short form #change="getMountain(formVariables.country_id);"
You should use the 'options' attribute in place of trying to v-repeat <option></option>:
VM
data: {
countryList: [
{ text:'United States',value:'US' },
{ text:'Canada',value:'CA' }
]
},
watch: {
'formVariables.country_id': function() {
// do any number of things on 'change'
}
}
HTML
<select
class="breadcrumb_mountain_property"
id="country_id"
v-model="formVariables.country_id"
options="countryList">
</select>
You can use select in this way. Remember to use array in v-for.
<select v-model="album.primary_artist">
<option v-for="i in artistList" :key="i.id" :value="i.name">
{{ i.name }}
</option>
</select>
You can use this way.
<select v-model="userData.categoryId" class="input mb-3">
<option disabled value="null">Kategori</option>
<option
v-for="category in categoryList"
:key="category.id"
:value="category.id"
>
{{ category.name }}
</option>
</select>
export default {
data() {
return {
categoryList: [],
userData: {
title: null,
categoryId: null,
},
};
},
The important thing here is what the categoryId value is, the default option value should be that.
categoryId: null,
<option disabled value="null">Kategori</option>
Here we use categoryId as value in v-model and initialize it with null. Value must be null in default option.
<select v-model="userData.categoryId" class="input mb-3">