Vue rendering of dynamic select options list - javascript

I have a Vue where the AJAX fetched select options are not available the FIRST time the Vue is rendered.
I have done as suggested here: https://v2.vuejs.org/v2/guide/list.html#Object-Change-Detection-Caveats.
The form is dynamically generated from a "schema" object.
<b-form-group v-for="(obj, key) in schema"
v-if="obj.type !== 'hidden'"
:label="createInputLabel(obj, key)" :label-for="key" >
...
<div v-else-if="obj.type == 'select'" role="group"
class="form-group">
<select v-model="attributes[key]" :id="key" :name="key"
class="custom-select">
<option v-for="option in optionsList[key]"
v-bind:value="option.value">
{{ option.text }}
</option>
</select>
</div>
...
</b-form-group>
optionsList is a keyed object containing lists of all the select elements on the form. optionsList is created by watching the "schema" object. The Rest API code is:
var idx = key;
Rest('GET', '/table/' + schema[key].table, requestParams,
this.$root.user.token)
.then(response => {
var localOptionsList = [];
response.data.forEach(function(row) {
var obj = { value: row[schema[idx].listColumns],
text: row[schema[idx].listColumns] };
localOptionsList.push(obj);
});
Vue.set(me.optionsList, idx, localOptionsList);
})
.catch(error =>
...
As mentioned, the code works fine, apart from the FIRST time the Vue is rendered.
This is obviously because the data has not yet been returned from the Rest API, so my problem is how to get the Vue to react when the data arrives?
Can anyone help?

Related

Vue JS Make drop-down select run query when option selected

I am very new to Vue and stuck on a problem. I have several drop-down select menus which return info from an Axios request The select t only works when I hit the submit button though. I would like it to run when the user makes the drop-down selection. I can't work out what I am missing. Can anyone help?
I have included some of the code below. Thanks
The html:
<div class="form-group col-md-6">
<label for="subject" class="control-label">Subject</label>
<select
v-model="subject"
class="form-control"
:disabled="selectDisabledSubject"
>
<option disabled value="">Select subject</option>
<option
v-for="subject in uniqueSubjects"
:key="subject"
:value="subject"
>
{{ subject }}
</option>
</select>
</div>
The vue
uniqueSubjects() {
const metaVFees = this.results
.filter((result) => result.metaData && result.metaData.V)
.map((item) => item.metaData.V)
.filter((subject, i, arr) => arr.indexOf(subject) === i);
// Split multiple subjects in strings and store in an array
let subjects = [];
metaVFees.forEach((item) => {
const splitArr = item.split(", ");
subjects = subjects.concat(splitArr);
});
return subjects
.sort()
.filter((subjects, i, arr) => arr.indexOf(subjects) === i);
},
You can use vue Watchers
watch:{
subject(newVal){
// call the function you normally call with submit click
}
}

Multiple :value in vue

I have this that takes the value and send it to my backend along with others in a form
<select
v-model="formData.account_bank"
id="branch"
#change="getbranch"
>
<option v-for="bank in selectBank" :key="bank.id" :value="bank.code">{{
bank.name
}}</option>
</select>
but before it is sent I need to get some data to populate a second selectbox using bank.id which I should have as :value = "bank.id" in the code above.
To get the value to make the api call that populates the second selectbox I just do
var branch = document.getElementById('branch').value;
and use it in the API call.
But here I need to get the bank.id to make the first api call that populates the selectbox and still have the bank.code there as form data to submit.
I have thought about getting the key using javascript which has the bank.id. But can not figure how or a different way it should work.
One solution to this problem is to bind the entire bank object to your <select> model.
Then you can use its id to populate your other select options and its code to construct your form data payload.
For example
<select
v-model="selectedBank"
id="branch"
#change="getbranch"
>
<option v-for="bank in selectBank" :key="bank.id" :value="bank">{{
bank.name
}}</option>
</select>
data: () => ({
formData: {
// whatever you originally had here
},
selectedBank: {} // empty object or perhaps a default bank
}),
methods: {
getBranch () {
const bankId = this.selectedBank.id
// make API call, etc
},
submitForm () {
// build form data
const formData = {
...this.formData,
account_bank: this.selectedBank.code
}
// submit form, etc
}
}
See https://v2.vuejs.org/v2/guide/forms.html#Value-Bindings

How to pre-populate ngselect in Angular?

I am grabbing some items through a http call and then want to pre-populate the ngselect, but using the abstractcontrol.setvalue() method does not seem to work.
Template Code
<ng-select [items]="cars"
bindValue="code"
bindLabel="displayName"
formControlName="car"
[clearable]="false"
[searchable]="false"
id="car"
placeholder="Select a car">
Component Code
this.setValueForPrePopulatedPlanningDetail('car', car);
private setDefaultValue(fieldName: string, value: any) {
if (value && value.length > 0) {
const field = this.myFormGroup.get(fieldName);
field.markAsDirty();
field.setValue(value);
}
}
You need set your result to the list (countries) and then use setvalue to choose the option
Set result to your list in your component:
this.countries = ['XPTO','XPTO2','XPTO3']
Define the following in your html:
<select>
<option [value]="country" *ngFor="let country of countries"> {{country}}</option>
</select>
The right way of using ng-select, would be to assign the values to the items input binding.
For instance,
<ng-select [items]="cities2"
bindLabel="name"
bindValue="id"
[multiple]="true"
placeholder="Select cities"
[(ngModel)]="selectedCityIds">
</ng-select>
And on your component.ts, you will populate ng-select options by subscribing to the observable returned by the HTTP request, and assigning it the cities2 property.
cities2: any[] = [];
ngOnInit() {
this.dataService.getData.subscribe(res => {
this.cities2 = res;
});
}
This demo might not directly answer your queries, but it shows how the various input bindings (such as items) work with ng-select.

handlebars with two different sets of properties. helper or partial?

I have a json object, let's call it teamData with Teams.
I want to be able to pass a variable such as Primary or Secondary to a call and have it render a states dropdown with the State of the TeamType selected.
I've been reading many handlebars tutorials but none of them really deal with more than one property of an object or show how to link a value from one property to another.
the states dropdown is simple
<script type="text/x-handlebars-template" id="tmpl-states">
<select>
{{#eachProperty States}}
<option name="{{property}}">{{value}}</option>
{{/eachProperty}}
</select>
</script>
Handlebars.registerHelper('eachProperty', function (context, options) {
var ret = "";
for (var prop in context) {
ret = ret + options.fn({ property: prop, value: context[prop] });
}
return ret;
});
but what I want to do is more like (in sudo)
renderTemplate("tmps-all", "container", "data", "variable");
<script type="text/x-handlebars-template" id="tmps-all">
{{#each Teams}}
{{#if TeamType == variable}} // e.g. Primary
var State = this.State;
{{/if}}
{{/each}}
<select>
{{#eachProperty States}}
{{#if property == State}} // e.g NY
<option name="{{property}}" selected>{{value}}</option>
{{/else}}
<option name="{{property}}">{{value}}</option>
{{/if}}
{{/eachProperty}}
</select>
</script>
var teamData = {"Teams":[{"TeamType":"Primary","State":"NY"},{"TeamType":"Secondary","State":"CA"}],"States":{"AK":"Alaska","AL":"Alabama","AR":"Arkansas","AZ":"Arizona","CA":"California","CO":"Colorado","CT":"Connecticut","DC":"District of Columbia","DE":"Delaware","FL":"Florida","GA":"Georgia","HI":"Hawaii","IA":"Iowa","ID":"Idaho","IL":"Illinois","IN":"Indiana","KS":"Kansas","KY":"Kentucky","LA":"Louisiana","MA":"Massachusetts","MD":"Maryland","ME":"Maine","MI":"Michigan","MN":"Minnesota","MO":"Missouri","MS":"Mississippi","MT":"Montana","NC":"North Carolina","ND":"North Dakota","NE":"Nebraska","NH":"New Hampshire","NJ":"New Jersey","NM":"New Mexico","NV":"Nevada","NY":"New York","OH":"Ohio","OK":"Oklahoma","OR":"Oregon","PA":"Pennsylvania","PR":"Puerto Rico","RI":"Rhode Island","SC":"South Carolina","SD":"South Dakota","TN":"Tennessee","TX":"Texas","UT":"Utah","VA":"Virginia","VT":"Vermont","WA":"Washington","WI":"Wisconsin","WV":"West Virginia","WY":"Wyoming"}};
There is no need for your eachProperty helper. The functionality it is giving you exists already in Handlebars. Let's remove that helper and update our template to the following (Note: I will replace the name attribute with value):
<select>
{{#each States}}
<option value="{{#key}}">{{this}}</option>
{{/each}}
</select>
Now on to the task of setting the selected attributed.
You are trying too much logic into your template. It is not for the template to initialize variables. That work should be done before the template is rendered. We want our code the calls the template method to give the template all the data it needs. This would mean passing to our template a data structure like this:
[
{
value: 'AK',
label: 'Alaska',
selected: false
},
{
value: 'AL',
label: 'Alabama',
selected: false
},
// and so on...
]
Our code will do the work of building this data structure:
var selected_team = teamData.Teams.find(team => team.TeamType === 'Primary');
var states = Object.keys(teamData.States).map(key => ({
value: key,
label: teamData.States[key],
selected: (key === selected_team.State)
}));
Now we can modify our template to handle our new data structure:
<select>
{{#each this}}
<option value="{{value}}" {{#if selected}}selected{{/if}}>{{label}}</option>
{{/each}}
</select>
When we call our template, we simply pass in our states variable:
renderTemplate(states);
However:
With all of that work behind us, I want to add that I see no purpose in re-rendering this template just to reflect a changed selected option. It makes more sense to me to use the DOM to make the change. Something like the following would suffice:
document.querySelector('#Select [value="NY"]').selected = true;
See: https://stackoverflow.com/a/7373115/3397771

Meteor Category and Subcategory Select Menu

I am new to Meteor but acquired some fair knowledge of this framework. I am creating one App in which I have to Build a Category Management Module, I am using a Category collection for this and in the document my values are like this
{
_id:
name:
parentID:
.....
}
I have tried few things to make it recursive, but failed to do it, what I need is a drop down which contains all the categories with their children. like this:
http://blog.digibali.com/wp-content/uploads/2011/03/menutree2.jpg
I would appreciate if anyone here can help in this issue:
Right Now what I am doing is fetching me to only 2 levels, I mean Top Parent and a Sub Child, I want unlimited levels for this, I know it might be possible through recursive function, but unable to find the way
Template:
<template name="categoryselect">
<select id="category" name="category" class="category">
<option value="">--Select--</option>
{{#each get_categories}}
<option value="{{_id}}">{{name}}</option>
{{#each get_sub_categories}}
{{> subcategoryselect}}
{{/each}}
{{/each}}
</select>
</template>
<template name="subcategoryselect">
<option value="{{_id}}">--{{name}}</option>
</template>
Template Helpers :
Template.categoryselect.helpers({
'get_categories': function(){
return Categories.find({parentID:''});
},
'get_sub_categories': function(){
return Categories.find({parentID:this._id});
}
});
Here's a tested solution:
html
<template name="categoryselect">
<select id="category" name="category" class="category">
<option value="">--Select--</option>
{{#each get_categories}}
<option value="{{_id}}">{{name}}</option>
{{/each}}
</select>
</template>
js
Template.categoryselect.helpers({
get_categories: function() {
var results = [];
var mapChildren = function(category, level) {
// add the appropriate number of dashes before each name
var prefix = Array(2 * level).join('--');
results.push({_id: category._id, name: prefix + category.name});
// repeat for each child category
var children = Categories.find({parentID: category._id}).fetch();
_.each(children, function(c) {
// make sure to increment the level for the correct prefix
mapChildren(c, level + 1);
});
};
// map each of the root categories - I'm unsure if the parent
// selector is correct or if it should be {parentId: {$exists: false}}
_.each(Categories.find({parentID: ''}).fetch(), function(c) {
mapChildren(c, 0);
});
// results should be an array of objects like {_id: String, name: String}
return results;
}
});

Categories

Resources