AngularJS ng-repeat questions and init inputs with matching answer object - javascript

I am using an ng-repeat to repeat through questions provided by an ajax response and I need the ng-model of each question input to point to a separate answers array.
The question array looks like this
bookingQuestions: [
0: {
label: 'Any allergies?',
type: 'text',
id: 1234
},
1: {
label: 'Names of attendees',
type: 'text',
id: 1235
}
]
I create the answers array by looping through all the questions and pushing the id and an empty answerValue property into my empty bookingAnswers array. The answers array looks like this:
bookingAnswers: [
0: {
id: 1234,
answerValue: ''
},
1: {
id: 1235,
answerValue: ''
}
]
In the markup, I'm attempting to init the answerObj by getting the correct answer object to match the corresponding question.
<div class="question" ng-repeat="question in bookingQuestions">
<label class="question-label" for="{{question.id}}">{{question.label}}
</label>
<input type="text" name="{{question.id}}" ng-model="answerObj"
ng-init="answerObj = getAnswerObj(question)">
</div>
The JS function:
$scope.getAnswerObj = function(question) {
angular.forEach(bookingAnswers, function(answer) {
if(question.id === answer.id) {
return answer.answerValue;
}
});
}
Even though the JS function successfully returns the correct object property, the ng-model isn't being updated to use it. How do I get this working?

You bind the ng-model of all your inputs to some answerObj meaning they all point to the same variable. Using $index you can access the index of the current iteration. So you could do something like this:
<input type=“text“ name="{{question.id}}"
ng-model="bookingAnswers[$index].answerValue"> </div>

<div class="question" ng-repeat="question in bookingQuestions">
<label class="question-label" for="{{question.id}}">{{question.label}}
</label>
̶<̶i̶n̶p̶u̶t̶ ̶t̶y̶p̶e̶=̶"̶t̶e̶x̶t̶"̶ ̶n̶a̶m̶e̶=̶"̶{̶{̶q̶u̶e̶s̶t̶i̶o̶n̶.̶i̶d̶}̶}̶"̶ ̶n̶g̶-̶m̶o̶d̶e̶l̶=̶"̶a̶n̶s̶w̶e̶r̶O̶b̶j̶"̶
<input type="text" name="{{question.id}}" ng-model="answerObj.answerValue"
ng-init="answerObj = getAnswerObj(question)" />
</div>
$scope.getAnswerObj = function(question) {
angular.forEach(bookingAnswers, function(answer) {
if(question.id === answer.id) {
̶r̶e̶t̶u̶r̶n̶ ̶a̶n̶s̶w̶e̶r̶.̶a̶n̶s̶w̶e̶r̶V̶a̶l̶u̶e̶;̶
return answer;
}
});
}

Related

v-if span not displaying when errors are filled vuejs

so I have an error array in data and whenever user focuses out of an input it checks if its empty. If it is empty it add's an object to the error array like so:
[
"0": {
"product_name": {
"message": "to polje je obvezno"
},
"barcode": {
"message": "to polje je obvezno"
}
},
"1": {
"barcode": {
"message": "to polje je obvezno"
}
},
"2": {
"product_name": {
"message": "to polje je obvezno"
}
}
]
so the 0,1,2 stand for the index of the item because I have a v-for loop and then product_name or barcode stand for the input in that item/index.(component is at the end of the post if you need it). So now I am trying to display an error when product_name or barcode exists.
I am trying like this:
<span class="tooltip"
v-if="errors && errors[index] && errors[index]['product_name']" style="left: 5px">
test123 (this is product_name error, above index is the current index in v-for so 0 or 1 or 2...)
</span>
<span class="tooltip"
v-if="errors && errors[index] && errors[index]['product_name']"style="left: 5px">
test123 (this is barcode error, above index is the current index in v-for so 0 or 1 or 2...)
</span>
but it doesnt display the span
component:
<tr v-for="(item, index) in documentItems" :key="item.id">
<td>{{index + 1}}.</td>
<td>
<div>
<textarea v-model="item.barcode"
#focusout="checkInput('barcode',index)"
cols="15" rows="2">
</textarea>
<span v-if="errors && errors[index] && errors[index]['barcode']">
test123
</span>
</div>
</td>
<td>
<div>
<textarea v-model="item.product_name"
#focusout="checkInput('product_name',index)"
cols="15" rows="2">
</textarea>
<span v-if="errors && errors[index] && errors[index]['product_name']">
test123
</span>
</div>
</td>
</tr>
EDIT: is it possible that my checkInput is the problem? this is how I created errors:
checkInput(name, itemIndex){
if(this.documentItems[itemIndex][name] == null){
this.errors[itemIndex][name] = { message: 'to polje je obvezno'}
};
//testing
console.log(this.errors[itemIndex][name]); //works
if(this.errors[1]['product_name']){
console.log("yes"); //works
}
},
EDIT2:
the spans show if I define error object like so:
errors: {
0: {
barcode: '',
product_name: ''
},
1: {
barcode: '',
product_name: ''
}
},
but if I do it with a for loop span don't show (I made a for loop in method where I retrive all the documentItems and gets fired on mounted()):
for(var i = 0;i < response.data.documentItems[0].length;i++){
this.errors[i] = {
barcode: '',
product_name: '',
}
}
Your problem roots in a vue reactivity caveat mentioned in their documentation.
https://v2.vuejs.org/v2/guide/reactivity.html#For-Objects
Vue will create proxy-like objects (a pattern similar to Observer using Object.defineProperty) for every field that is defined in your data function before anything runs, when you manually add fields using this.foo = bar (or something similar), if 'foo' key is not already available in your data field, vue will not make it reactive, hence it will not update your DOM when it changes.
You can achieve what you want in a couple of workarounds.
First way which also mentioned in their documentations is to create whole errors object with Object.assign or spread syntax and re-assign your field in data.
// instead of `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
This solution is similar to treating a field like its immutable
So you can fix your checkInput method with the following change:
checkInput(name, itemIndex){
if(this.documentItems[itemIndex][name] == null){
const newErrorForName = { [name]: { message: 'to polje je obvenzo' }};
this.errors = Object.assign({}, {...this.errors, [itemIndex]: newErrorForName })
};
//testing
console.log(this.errors[itemIndex][name]); //works
if(this.errors[1]['product_name']){
console.log("yes"); //works
}
},
This is because vue cant understand manual object property add/delete.
Your second way is to use an array for errors instead of an object.
This is probably a better idea since your errors object is really an array.
it has fixed integer zero based indexes!

Vuejs : using the same array of data for two different lists of inputs

I have created a fiddle to explain what I want : https://jsfiddle.net/silentway/aro5kq7u/3/
The standalone code is as follows :
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="mainApp" class="container-fluid">
<p>This is my main list.</p>
<div class="main-list" v-for="(q, index) in questions">
<input type="checkbox"
v-bind:id="'question-' + index"
v-bind:value="{id: index, property: false}"
v-model="answers">
<label v-bind:for="q">{{q}}</label>
</div>
<p>And this is the list of the selected elements in the previous list.</p>
<ul class="selected-list" v-for="(a, index) in answers" :key="a.id">
<li>{{questions[a.id]}} <input type="checkbox"
v-bind:id="'answer-' + index"
v-bind:value="true"
v-model="a.property">
</li>
</ul>
<p>Here's the answer's array for debugging: {{answers}}</p>
</div>
<script>
var mainApp = new Vue({
el: '#mainApp',
data: {
questions: [
"Who are you ?",
"Who, who?",
"You know my name?",
"Look up my number"
],
answers: []
}
});
</script>
I want to display a first list of questions, each with a checkbox. The selected questions are stored in an array called "answers".
From these selected answers I then make another list. Each item has a new corresponding checkbox, for a certain property (which can be true or false). I would like this associated property to be stored in the same array ("answers") as the results from the input in the first list.
What happens with my code is that checking a box in the second list does change the shared array of data ("answers"), but in doing so it also unchecks the corresponding answer in the first list.
Any help would be much appreciated.
I'm having a very hard time following your wording but I gave it a shot anyway. I think you'd be better off keeping selected questions and selected answers in their own array and use a computed property to join them basically. Here's a quick fiddle of it: https://jsfiddle.net/crswll/d8e1g750/21/
new Vue({
data: {
questions: [{
id: 1,
question: 'What the heck 1?'
},
{
id: 2,
question: 'What the heck 2?'
},
{
id: 3,
question: 'What the heck 3?'
},
{
id: 4,
question: 'What the heck 4?'
},
{
id: 5,
question: 'What the heck 5?'
},
],
selectedQuestions: [],
selectedAnswers: [],
},
computed: {
answers() {
return this.selectedQuestions.map(id =>
this.questions.find(question => question.id === id)
)
},
selectedAnswersSimpleList() {
return this.selectedAnswers
.map(id => this.questions.find(question => question.id === id))
.map(question => question.question)
}
},
}).$mount('#app')

Loop nested objects AngularJS [duplicate]

This question already has answers here:
Iterate through nested json array in angular controller and get unique values
(5 answers)
Closed 4 years ago.
{
name: 'Product One',
visibility: 1,
weight: '0.5',
price: '19.99'
custom_attributes: [
{
attribute_code: 'image',
value: '.img'
},
{
attribute_code: 'special_price',
value: '13.99'
}
]
},
{
name: 'Product One',
visibility: 1,
weight: '0.5',
price: '19.99'
custom_attributes: [
{
attribute_code: 'image',
value: '.img'
},
{
attribute_code: 'special_price',
value: '13.99'
}
]
}
How do I get access to 'special_price' value on ng-repeat or javascript?
There are few ways you could approach this.
Prepare the data in the controller
function ExampleCtrl (products) {
this.$onChanges = (changes) => {
if (changes.products) {
this.specialProductPrices = this.products.reduce((map, product) => {
map[product.id] = product.custom_attributes
// #TODO: Account for a case when there is no special price.
.find(({attribute_code}) => attribute_code === 'special_price').value;
return map;
}, {})
}
}
}
angular.module('foo').component('example', {
bindings: {
products: '<'
},
controller: [
ExampleCtrl
],
template: `
<div ng-repeat="product in $ctrl.products track by product.id">
Name: <span ng-bind="product.name"></span>
Price: <span ng-bind="product.price"></span>
Special Price: <span ng-bind="$ctrl.specialProductPrices[product.id]"></span>
</div>
`
})
And then the component can simply be used as <example products="products"></example>. This is mostly an idiomatic approach in angularjs land,
and overall encouraged due to the use of components + reducers which prepare the data once rather than looping multiple times during every $digest cycle.
Access it within the loop in template
If you have to do this in the template, you could do something like this:
<div ng-repeat="product in $ctrl.products track by product.id">
Name: <span ng-bind="product.name"></span>
Price: <span ng-bind="product.price"></span>
Special Price:
<span>
<span ng-repeat="customAttribute in product.custom_attributes track by customAttribute.attribute_code"
ng-show="customAttributeattribute_code === 'special_price'"
ng-bind="customAttribute.value">
</span>
</span>
</div>
However this method isn't encouraged, since it creates DOM elements which will never be shown (ng-if can't be used in this case due to the repeater.). Additionally if there are many custome attributes,
this will become very inefficient.
Other options
One could also potentially create components which take in product.custom_attributes and show the attribute if present, or create a filter which would pick the attribute out as well.
These methods are left as an exercise to the reader.

update scope object with filter - AngularJS

I need to update assign filtered objects from $scope.Departments to $scope.FilteredDepartments on change text field.
HTML:
<input type="text" ng-change="searchItems(searchquery)" ng-model="searchquery">
AngularCode:
$scope.Departments= [{id: 1, name:'ABC'},{id: 2, name:'XYZ'}...];
$scope.searchItems = function (searchString) {
//Need to do something here
}
How do I filter with name and assign filtered result to $scope.FilteredDepartments ?
Thanks for help
HTML:
<input type="text" ng-model="searchValue">
<h3>Filter Ones</h3>
<div ng-repeat="department in filteredDepartments">
<p>{{department.name}}</p>
</div>
JS:
$scope.departments= [{id: 1, name:'ABC'},{id: 2, name:'XYZ'}];
$scope.$watch(function () {
return $scope.searchValue;
}, function () {
$scope.filteredDepartments = $filter('filter')($scope.departments, $scope.searchValue);
});
don't forget to inject $filter into the controller

How to generates dynamically ng-model="my_{{$index}}" with ng-repeat in AngularJS?

I would like to ask you if you can give me a hand on this.
I have created a jsfiddle with my problem here. I need to generate dynamically some inputs with ng-model in a ng-repeater using the way ng-model="my_{{$index}}".
In jsfiddle you can see that everything it's working fine until I try to generate it dynamically.
The html would be:
<div ng-app>
<div ng-controller="MainCtrl">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td>
<select ng-model="selectedQuery"
ng-options="q.name for q in queryList" >
<option title="---Select Query---" value="">---Select Query---</option>
</select>
</td>
</tr>
<tr ng-repeat="param in parameters">
<td>{{param}}:</td>
<td><input type="text" ng-model="field_X" />field_{{$index}}</td>
</tr>
</table>
<div>
<div>
And the javascript...
function MainCtrl($scope) {
$scope.queryList = [
{ name: 'Check Users', fields: [ "Name", "Id"] },
{ name: 'Audit Report', fields: [] },
{ name: 'Bounce Back Report', fields: [ "Date"] }
];
$scope.$watch('selectedQuery', function (newVal, oldVal) {
$scope.parameters = $scope.selectedQuery.fields;
});
}
Can you give me any idea?
Thanks a lot.
Does it solve your problem?
function MainCtrl($scope) {
$scope.queryList = [
{ name: 'Check Users', fields: [ "Name", "Id"] },
{ name: 'Audit Report', fields: [] },
{ name: 'Bounce Back Report', fields: [ "Date"] }
];
$scope.models = {};
$scope.$watch('selectedQuery', function (newVal, oldVal) {
if ($scope.selectedQuery) {
$scope.parameters = $scope.selectedQuery.fields;
}
});
}
And in your controller:
<tr ng-repeat="param in parameters">
<td>{{param}}:</td>
<td><input type="text" ng-model="models[param] " />field_{{$index}}</td>
</tr>
Fiddle
What you could do is to create an object on a scope (say, values) and bind to the properties of this object like so:
<input type="text" ng-model="values['field_' + $index]" />
Here is a jsFiddle illustrating the complete solution: http://jsfiddle.net/KjsWL/
I did elaborate my answer from pkozlowski's and try to generate a dynamic form, with dynamic ng-model:
<form ng-submit="testDynamic(human)">
<input type="text" ng-model="human.adult[($index+1)].name">
<input type="text" ng-model="human.adult[($index+1)].sex">
<input type="text" ng-model="human.adult[($index+1)].age">
</form>
But first, we need to define the 'human' scope inside our controller
$scope.human= {};
And then, on submission we will have the data like this (depending on how much field is generated):
var name = human.adult[i].name;
var sex = human.adult[i].sex;
var age = human.adult[i].age;
It's pretty straightforward and I hope my answer helps.
Is there a reason to generate those field names? Can you treat each field as an object with name and value instead of a string name? (FIDDLE)
function MainCtrl($scope) {
$scope.queryList = [
{ name: 'Check Users', fields: [ { name: "Name" }, { name: "Id" } ] },
{ name: 'Audit Report', fields: [] },
{ name: 'Bounce Back Report', fields: [ { name: "Date" } ] }
];
}
And just repeat off of selectedQuery.fields:
<tr ng-repeat="field in selectedQuery.fields">
<td>{{field.name}}:</td>
<td><input type="text" ng-model="field.value" /></td>
</tr>
Beterraba's answer was very helpful for me. However, when I had to migrate the solution to Typescript it wouldn't behave for me. Here is what I did instead. I expanded the individual parameters (fields on the queryList in your example) into full objects that included a "value" field. I then bound to the "value" field and it worked great!
Originally you had:
[
{ name: 'Check Users', fields: [ "Name", "Id"] },
...
}
]
I changed it to something like this:
[
{ name: 'Check Users', fields: [
{Text:"Name",Value:""},
{Text:"Id",Value:0}],
...
]
}
]
...and bound to the 'Value' sub-field.
Here is my Typescript if you care.
In the html:
<div ng-repeat="param in ctrl.sprocparams" >
<sproc-param param="param" />
</div>
In the sproc-param directive that uses Angular Material. See where I bind the ng-model to param.Value:
return {
restrict: 'E',
template: `
<md-input-container class="md-block" flex-gt-sm>
<label>{{param.Name}}</label>
<input name="{{param.Name}}" ng-model=param.Value></input>
</md-input-container>`,
scope: {
param: "="
}
}
I need to generate dynamically some inputs with ng-model in a ng-repeater using the way ng-model="my_{{$index}}".
One can use the this identifier with a bracket notation property accessor:
<tr ng-repeat="param in parameters">
<td>{{param}}:</td>
<td>
<input type="text" ng-model="this['my_'+$index]" />
my_{{$index}}
</td>
</tr>
It is possible to access the $scope object using the identifier this.
For more information, see
AngularJS Developer Guide - Expression Context

Categories