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
Related
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?
In web app I have a list of Expenses, which that if you press above it takes you to a new page to edit the selected Expense. When the user click on an element of list, I save the Object (the selected Expense) in a Global Object common in whole application and I retrieve it when I am in the Editing controller:
$scope.companySelected = GlobalApplicationData.selectedExpenseList;
The GlobalApplicationData.selectedExpenseList is an Object:
$$hashKey: "object:39"
_id: "33aa5549-7802-40d9-bc89-8780705b8c9b"
_rev: "3-eb940cb990524112723f711618e0cf51"
ad_table_id: 1000596
company_name: "Company 1"
data: Object
recordid: 1000005
__proto__: Object
In one expense there is only one company.
So, in this page of editing I have some field (input type text, date, etc). And I also have a selection with the list of the Companies of the logged user. To retrieve that list, I made this function:
$scope.getCompanyList = function() {
EditExpenseListSrv.getCompanyListDetail(user).then(function (companyListDetail) {
$scope.companyList = companyListDetail;
$scope.$apply();
}).catch(function (err) {
console.log(err);
});
};
CompanyListDetail is an array of Obejct:
companyListDetail: Array[5]
0: Object
1: Object
2: Object
3: Object
4: Object
And this is one example of those object:
_id: "44e620dc-e715-453f-882b-3f21aeef48fe"
_rev: "1-c09c9f3350e853e588f3358aaafc0374"
ad_table_id: 1000146
data: {
description: "text"
id_company: 513424
name: "Company 2"
}
recordid: 1000002
So the company of the selected Expense ($scope.companySelected) will definitely part of the list obtained by the query ($scope.companyList). But I want to give the user the possibility to change it.
So I would like to make a selection containing the list ($scope.companyList) and default already selected the Company corresponding to $scope.companySelected.
I've writter this, but it doesnt work:
<select class="selection" ng-model="companySelected"
ng-init="companySelected = globalApplicationData.selectedExpenseList"
ng-options="company
as company.data.name for company in companyList">
But it doesn't work. I can see in the selection all the Companies, but don't select the default value
When you are assigning a default value to the select, you need to assign the object, so it won't work, when assigned with the same data of some other object:
Note:
a) data is the array which is getting looped in the select list
b) selectedcountry is the model which is bind on select list.
Option 1:
Using ng-init:
<select ng-model="selectedcountry" ng-init="selectedcountry = data[0]" ng-options="item.name for item in data">
<option value="">Select</option>
</select>
Demo 1.
Option 2:
Assigning the default from the controller
$scope.selectedcountry = $scope.data[0];
Demo 2
You can try to do the initialisation in the controller:
$scope.companySelected = "the value that you want to set here"
Or you can try to put the ng-init inside the parent of the select. Once I had a problem like this and I solved it putting the ng-init in the parent tag. Example:
<div id="parent" ng-init="companySelected = globalApplicationData.selectedExpenseList">
<select ...>
</div>
Another idea would be to put companySelected inside an object. I have had some problems (I am not sure why) with the forms if I was using $scope.value inside the ng-value instead of using $scope.formData.value
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;
}
});
I am brand new, just experimenting, with AngularJS framework. So I am not sure the approach I have taken is the best/right way.
I'm trying to create a 3 level chained ajax filled select boxes, and it is kind of working, except for a couple of issues.
My actual code uses ajax which is in a factory, but for the sake of the fiddle I just have a controller returning results from an array.
Demo (using arrays, not ajax): http://jsfiddle.net/xxwe1zu8/1/
Issues:
2nd and 3rd level selects don't have a "selected" attribute - ie the one you select doesn't get highlighted.
Ideally I would like the top level categories to be dynamically filled on page load via ajax (or in this example by array) using the same angular function (eg: getSubcategories = function(0, 0) {) rather than hardcoded. Top level categories have a parent of 0.
Bonus: Can the 3rd select box only be shown/visible if there is a sub sub category returned after selecting the sub category? In reality, most sub categories won't have sub sub categories.
var myAppModule = angular.module('myApp',[]);
myAppModule.controller('SearchCtrl', function($scope) {
var self = this;
self.subCategories = [];
self.getSubcategories = function(parent, level) {
theCategories = [];
angular.forEach(sub_category[parent], function(idx, val) {
theCategories.push({id: val, name: idx});
});
self.subCategories[level] = theCategories;
}
});
Thanks
I would suggest to restructure data in an array of objects and get rid of indicies which are seemingly of no use here.
var transport = [
{
name: "Cars",
models: [
{
name: "Hatchback",
variations: ["4 door", "5 door"]
},
{
name: "Sedan",
variations: ["4 door", "5 door"]
},
{
name: "SUV",
variations: ["Medium", "Large"]
}
]
},
...
This allows to make clearer code in template. I am not ControllerAs syntax here since $scope is injected in any way. And it is a better approach if you just start learning AngularJS
<select
ng-model="selectedType"
ng-options="t.name for t in transport"
ng-change = "selectedModel=null;selectedVariation=null">
<option value="">Select type</option>
</select>
<select
ng-model="selectedModel"
ng-options="model.name for model in selectedType.models"
ng-change="selectedVariation=null"
ng-show="selectedType.models">
<option value="">Select model</option>
</select>
<span ng-show="loading">Loading...</span>
<select
ng-model="selectedVariation"
ng-options="variation for variation in variations"
ng-show="variations && !loading">
<option value="">Select variation</option>
</select>
selectedType, selectedModel and selectedVariation are implicitly defined in $scope by ng-model of each select. There is even no mention of them in controller currently. At the same time this properties are used in ng-show to hide select tags which are not relevant in current selection.
The last select (sub subcategory) demonstrates a way to fetch data asynchronously. Let's imagine you don't have a complete tree of data at once and fetch variations from server on model selection. You would place a watch in controller for selectedModel and if it was selected (not emptied) you would launch a request for data and update variations on response
$scope.$watch('selectedModel', function(model){
if(model){
$scope.loading = true;
//setTimeout imitates an ajax request
setTimeout(function(){
$scope.variations = model.variations;
$scope.loading = false;
$scope.$apply();
}, 1000);
}
})
Updated fiddle
Trying to dynamically create a select input with options from a json array of objects.
The JSON array of objects looks like this:
var curr_sel = [
{v:"1", n:"USD"},
{v:"2", n:"GBP"},
{v:"3", n:"CAD"},
{v:"4", n:"AUD"},
{v:"5", n:"EUR"}
];
And my view template has the following:
<select>
{{#each opt in curr_sel}}
<option value="{{ opt.v }}" >{{ opt.n }}</option>
{{/each}}
</select>
But that produces a select element with no options. Kindly help. Even better if it means using the EmberJS select class http://emberjs.com/api/classes/Ember.Select.html
The better way to use the select tag is using Ember.Select.
Here is a sample:
Template
<script type="text/x-handlebars" data-template-name="application">
{{view Ember.Select content=curr_sel
optionLabelPath="content.n"
optionValuePath="content.v"
selection=selectedCurrency}}
</script>
Javascript
App = Ember.Application.create({});
App.ApplicationController = Ember.Controller.extend({
curr_sel: [
{v:"1", n:"USD"},
{v:"2", n:"GBP"},
{v:"3", n:"CAD"},
{v:"4", n:"AUD"},
{v:"5", n:"EUR"}
],
selectedCurrency: null,
selectedCurrencyChanged: function() {
console.log(this.get('selectedCurrency.n'));
}.observes('selectedCurrency')
});
Give a look in this fiddle to see this in action http://jsfiddle.net/marciojunior/FJkEr/