Note: I'm quite new to angularjs
What is the best solution/practice for problem:
I have an array or typed values, for each type there should be different input(template and input validation)?
E.g. and simplified
var vars = [
{
type: 'int',
value: 42,
min: 0,
max: 42
},
{
type: 'text',
value: 'foobar'
},
]
for 'int' template will be
<input type="range" max="{{max}}" min="{{min}}" value="{{value}}" />
and for 'text'
<textarea>{{value}}</textarea>
In real case there will be quite many inputs with weird interfaces
An ng-switch (docs) can help you out here; something like this:
<div ng-repeat="item in items">
<div ng-switch on="item.type">
<div ng-switch-when="int">
<input type="range" max="{{item.max}}" min="{{item.min}}"
ng-model="item.value" />
</div>
<div ng-switch-when="text">
<textarea ng-model="item.value"></textarea>
</div>
</div>
</div>
[Update]
Since you mentioned you want to dynamically include a template based on the type, take a look at ng-include (docs) which takes an Angular expression evaluating to a URL:
<div ng-repeat="item in items">
<div ng-include="'input-' + item.type + '-template.htm'"></div>
</div>
If you don't like the inline string concatenation, you can use a controller method to generate the URL:
<div ng-repeat="item in items">
<div ng-include="templatePathForItem(item)"></div>
</div>
The example on the ngInclude documentation page is pretty good.
Note that the included template will be given a prototypal child scope of the current scope.
Related
I am writing a component (form) that acts as a container for form fields that are passed from the parent component.
I pass the form fields to the child as a prop:
formFields: [
{ name: 'email', type: 'email' },
{
name: 'sub_group',
type: 'group',
contents: [
{ name: 'sub_field_01', type: 'text' },
{ name: 'sub_field_02', type: 'text' },
]
}
]
That is all displayed fine and works well in my child component.
I used a computed property to build up data to post to a route to update the resource. I group the sub_group so that when I am posting, the controller understands that the sub_group is an array.
However, the sub_group array is always empty until I console log it, so it only evaluates when logged. The top level (email in this case) always shows up.
I have tried getting rid of the computed property and use a method to build up the object to post but I have the same issue. I really have no idea what is happening. How can 'force' the computed property to evaluate?
EDIT Adding form example
Code that renders the form:
<div v-for="field in formFields">
<!-- Individual Field -->
<div v-if="field.type !== 'group'">
<label v-if="field.label">{{ field.label }}</label>
<input :type="field.type" :name="field.name" v-model="field.value">
</div>
<!-- Group -->
<div v-else>
<div v-for="child in field.group">
<label v-if="child.label">{{ child.label }}</label>
<input :type="child.type" :name="child.name" v-model="child.value">
</div>
</div>
</div>
Computed property for post:
<div v-for="field in formFields">
<!-- Individual Field -->
<div v-if="field.type !== 'group'">
<label v-if="field.label">{{ field.label }}</label>
<input :type="field.type" :name="field.name" v-model="field.value">
</div>
<!-- Group -->
<div v-else>
<div v-for="child in field.group">
<label v-if="child.label">{{ child.label }}</label>
<input :type="child.type" :name="child.name" v-model="child.value">
</div>
</div>
</div>
I realise this needs refactoring but I can't actually get it working like this. I have tried adding cache:false and a getter to the computed property but that didn't work.
$(parentQuestion.children('.choice_class:last'));
<div class='parentquestion'> .....
<div class="choice_class">
<div class="form-group">
<label class="control-label col-md-3 col-sm-3 col-xs-12"><span class="choice-no">Option 1</span><span class="required">*</span>
</label>
<div class="col-md-6 col-sm-6 col-xs-12">
<input type="text" required="required" class="form-control col-md-7 col-xs-12">
</div>
<span class="glyphicon glyphicon-plus"></span>Add Choice
</div>
</div>
inside addOption function, using the this variable, I found the parent object and added another option below the last .choice_class.
I need to change the Option 1 to Option 2 when the second option is added.
The complete function is
function addOption(that) {
$(that).parent().parent().after($('.choice_class_sample .choice_class').clone());
var parentQuestion = $(that).parent().parent().parent();
var lastChoice = parentQuestion.children('.choice_class:last');
$(parentQuestion.children('.choice_class:last-child .choice-no')).html(parentQuestion.children('.choice_class').length);
$(that).remove();
}
But I am not able to select further more after selecting the children.
I could modify your code to "make it work", but the way you're going about this is a little bit unorthodox – namely, using clone to copy the existing element before duplicating it.
If you're going to be representing a JavaScript data structure (like an array) in DOM, and wanting to keep them in sync, it'd be much better to use a templating system. Since you're using jQuery, the jQuery Template plugin seems decent. From skimming the README, it would make your stuff as simple as this:
<script type="text/html" id="template">
<div>
<div>
<label>
<span>Option <span data-content="id"></span></span>
<span class="required">*</span>
</label>
<div>
<input type="text" required="required">
</div>
<span class="glyphicon glyphicon-plus"></span> Add Choice
</div>
</div>
</script>
Then, in your JavaScript, you'd have something like:
var options = [ { id: 1 } ]
parentQuestion.loadTemplate("#template", questions)
$('.add-choice-button').on('click', function () {
options.push({
id: options.length + 1
})
parentQuestion.loadTemplate('#template', options)
})
Much, much cleaner. And best of all, it avoids having ugly, imperative DOM manipulation in your JS.
I have a object which holds the key and value pair.
$scope.groups= {
1050 : 'Test',
1850 : 'Test1'
}
$scope.AnotherArray = [1050,1850];
item from ng-repeat is passed to the object as key to obtain the text 'Test'
<div ng-repeat="item in AnotherArray">
<input type="text" ng-model="groups[item]" />
</div>
Is there a way in angular to do this ?
As you were asking for an "Angular-way" to do this, here is a mildly modified version of Angular's ngRepeat example:
<div ng-repeat="(key, obj) in groups">
[{{key}}] {{obj}}
<input type="text" ng-model="obj"/>
</div>
http://plnkr.co/edit/0IRvLZpbUZUQBXOnebOt?p=preview
It makes use of Angular's internal conversion of array-like objects.
Link to Angular's ngRepeat-documentation: https://docs.angularjs.org/api/ng/directive/ngRepeat
I'm trying to get ng-repeat to repeat a list of items, spiffs, in a receipt. I have the edit function of this working but I can't get the add page to work because it reads:
TypeError: Cannot read property 'spiffs' of undefined
Here is the code causing it:
$scope.model.spiffs = [];
$scope.model.spiffs = [{ spiff_id: 0, receipt_id: 0, product_id: 0, spiff_amt:0, quantity: 1, active: true, note: null }];
At first it was the second line but I thought I'd try making the $scope.model an empty array to try and declare the array ahead of time, I tried different variations of this and I think I'm just missing something simple. What do I need to do to setup my form to ng-repeat a set of fields when I don't have any initial input to populate it with? And after that, will I be able to grab all the form data with a simple $scope.model call and send it to my API?
Here is the hope-to-be effected HTML:
<div class="col-xs-10 col-xs-offset-2" ng-repeat="item in model.spiffs">
<div ng-hide="item.active === false">
<div class="col-xs-4 form-group">
<select name="productID" ng-model="item.product_id" required="" ng-change="changeSpiffProduct($index)" class="form-control"
ng-options="product.product_id as product.model_number + ' ' + product.name for product in products">
</select>
</div>
<div class="col-xs-2 form-group">
<input name="spiff_sale_price" type="text" placeholder="" ng-model="item.quantity" class="form-control input-md">
</div>
<div class="col-xs-2 form-group">
<p name="spiff_amt" class="form-control-static">{{item.spiff_amt | currency: "$"}}</p>
</div>
<div class="col-xs-2 form-group">
<p name="spiff_total" class="form-control-static">{{item.spiff_amt * item.quantity | currency: "$"}}</p>
</div>
<div class="col-xs-2 form-group">
<button class="btn btn-danger" novalidate ng-click="removeSpiff($index)"><i class="fa fa-minus"></i></button>
</div>
</div>
</div>
Thank you very much in advance for the help!
You need to define the property "model" of your scope first before you can assign "spiff" to it. You can do that in two ways:
$scope.model = {};
//OR:
$scope.model = {
spiffs: []
};
You are writing $scope.model.spiffs. Here model is undefined, hence the error.
TypeError: Cannot read property 'spiffs' of undefined
You need to initialize $scope.model first, like this
$scope.model = {}
Now since $scope.model is not undefined, you can add a property.
Hope it helps.
I read through some articles on angular model binding, and just out of curiousity, I was wondering if its possible to bind keys to input too,
http://jsfiddle.net/x3azn/jM28y/4/
so I am hoping ot update the main arr through the input boxes and achieve 2 way key-binding.
Is this possible?
as explained here Binding inputs to an array of primitives using ngRepeat => uneditable inputs, yes you can, but not that way
try this
function ctrl($scope) {
$scope.arr = [{name:'1', lastname: '2'},
{name:'3', lastname: '4'},
{name:'5', lastname: '6'}]
}
<div ng-repeat="person in arr">
<input type="text" ng-model="person.name" />
<input type="text" ng-model="person.lastname" />
</div>
http://jsfiddle.net/jM28y/5/
No, it is not possible to bind a key to an input.
The closest thing that I found you can do is abuse ngRepeat's $index property and bind it to the input. You can't change keys for existing values but you can change what value is shown as well as create new key-value pairs. By no means am I recommending this as a solution, I just wanted to share the hackery that ensued when I was investigating this question.
JSFiddle: http://jsfiddle.net/DanielBank/v6tFG/
JavaScript:
function ctrl($scope){
$scope.obj = {
'0': 'a',
'1': 'b',
'2': 'c',
'George': 'Clooney',
};
}
HTML:
<div ng-app>
<div ng-controller="ctrl">
<div ng-repeat="value in obj">
<input type="text" ng-model="$index"/>
<input type="text" ng-model="obj[$index]"/>
<input type="text" ng-model="value"/>
</div>
{{obj}}
</div>
</div>