I have a problem when implementing a nested list in Angular: the view gets updated properly but, on the other side, the code is not updated on change.
I think it will be much clearer with the code:
_this.categories = injections.map(function (category) {
return {
title: category.get('title'),
object: category,
criteria: category._criteria.map(function (oneCriteria) {
return {
object: oneCriteria,
type: oneCriteria.get("type"),
min: _this.range(oneCriteria.get("range")).min,
max: _this.range(oneCriteria.get("range")).max,
key: oneCriteria.get("key"),
value: _this.range(oneCriteria.get("range")).min,
defaultValue: _this.range(oneCriteria.get("range")).min,
selected: false
}
})
}
});
_this.category = _this.categories[0];
_this.job = {
title: '',
description: '',
salaryAmount: 0,
salaryTimeUnit: _this.salaryTimeUnits[0],
category: _this.category.object,
criteria: _this.category.criteria,
location: {latitude: 48.137004, longitude: 11.575928}
};
So and, very quick here is my HTML:
<div ng-repeat="category in controller.categories">
<input type="radio" name="group" ng-value="category.object.get('title')" id="{{category.object.get('title')}}"
ng-checked="controller.category == category" ng-click="controller.category = category">
{{category.title}}
</div>
<br>
Criteria:
<div ng-repeat="criterium in controller.category.criteria">
<div class="row vertical-align">
<div class="col-xs-9">
<span ng-click="criterium.selected = !criterium.selected"
ng-class="['list-group-item', {active:criterium.selected == true}]">{{criterium.key}}</span>
</div>
</div>
</div>
The problem is the following: the value are getting updated in the view (when you click on a radio button on the category, you see the corresponding criteria(s)). But the job is for one reason that I ignore not updated although it has the same reference as the HTML (a reference to this category.criteria).
Did I miss something?
controller.job.criteria is still just a reference to controller.categories[0]. Your code should successfully update controller.category to point at whichever category you clicked on, but that does not also update the reference in your job data structure.
What you want to do is make your ngClick event a bit more robust, i.e.:
<input type="radio" ng-click="controller.updateCategory(category)" />
and then in your js:
_this.updateCategory = function (category) {
_this.category = category;
_this.updateJob(category);
};
_this.updateJob = function (category) {
_this.job.category = category.object;
_this.job.criteria = category.criteria;
};
This will update the references in your job to match the new jazz.
I would, however, recommend leveraging ngModel and ngChange in your radios instead. Like:
<input type="radio" ng-model="controller.category" ng-value="category" ng-change="updateJob(category)" /> {{category.title}}
Related
On my page I have angular ui accordion, inside of each panel, I'm rendering list with items and checkboxes, also I have checkbox "select all".
For selection method and logic I used this resource. In this resource logic seems working, but however I'm putting this logic to my code, it stops working.
What I want to achieve is when all checkboxes are selected, checkbox "select all" has been selected automatically, and if some of checkboxes is unselect, checkbox "select all" has to be unselect as well.
I have tried multiple suggestions provided here, here, here, but in the end I'm getting the same result.
I appreciate if somebody could help me to resolve my problem.
$scope.categories = [
{
id: 1,
name: "category 1"
},
{
id: 2,
name: "category 2"
},
{
id: 3,
name: "category 3"
}
]
$scope.selectedAll = false;
$scope.selectAll = function(array) {
$scope.selectedAll = !$scope.selectedAll;
angular.forEach(array, function(item) {
item.Selected = $scope.selectedAll;
});
};
$scope.checkIfAllSelected = function(array) {
$scope.selectedAll = array.every(function(item) {
return item.Selected == true
})
};
html
<div>
<div class="row" ng-class="{'selected': selectedAll, 'default': !selectedAll}">
<div>Select all
<input type="checkbox"
ng-model="selectedAll" ng-click="selectAll(categories)" >
</div>
</div>
<div class="row" ng-repeat="item in categories | orderBy : 'id'" ng-class="{'selected': item.selected, 'default': !item.selected}">
<div > {{ item.name }}
<input type="checkbox"
ng-model="item.Selected" ng-click="checkIfAllSelected(categories)"
>
</div>
</div>
This is my plunker
Please take a look at this fork of your plunker: https://plnkr.co/edit/OW3F1VMke9iLuNkt5p3o?p=preview
Two things:
1. It's a good practice to create an object to your view model (you can find it under the name model in the plunker $scope.model. This will solve 2 way data binding issues.
2. I have changed the ng-click to ng-change (this is not part of the solution though - its just more correct in my opinion).
Please let me know if you need more clarifications.
HTML:
<div class="panel panel-default" ng-repeat="(section, sectionData) in report">
<div class="panel-heading">{{sectionData.text}}</div>
<div class="panel-body">
<div class="row" ng-repeat="(part, partData) in sectionData.attr">
<div class="col-md-2">
<label>{{partData.text}}</label>
</div>
<div class="col-md-10">
<div class="form-group">
<div class="radio-inline" ng-repeat="condition in radioValues">
<label class="radio-inline">
<input type="radio" name="{{section}}-{{part}}" ng-value="{{condition.value}}" ng-model="partData[model]">
{{condition.text}}
</label>
</div>
</div>
</div>
</div>
</div>
</div>
JS model:
$scope.radioValues = [{
text: 'Good',
value: '1'
}, {
text: 'Average',
value: '2'
}, {
text: 'Needs Improvement',
value: '3'
}];
$scope.report = {
card: {
text: 'Card',
attr: {
front: {
text: 'Front',
model: 'detail.report.card.front',
},
rear: {
text: 'Rear',
model: 'detail.report.card.front.rear'
},
assembly: {
text: 'Assembly',
model: 'detail.report.card.front.assembly'
}
}
} //, and a lot of others like card
};
// Instantiate the model so that values are preselected
for (var section in $scope.report) {
for (var part in $scope.report[section].attr) {
initModel($scope.report[section].attr[part]); // basically sets the model values defined in $scope.report to 1
}
}
The $scope.report object is used to create the html and I'm trying to set the value of ng-model in the html to strings defined in the $scope.report. Along with that, I'm also trying to set the default values of each set of radios.
Is the ng-model="partData[model]" part correct? After setting the model values in the controller, the radios aren't preselected when the page loads. The model defined in the $scope.report should bind to the $scope directly. E.g. detail.report.card.front.assembly should actually become $scope.detail.report...
How do I make this work? Is it the right use of angular? Better alternatives?
I was able to get this done using a directive with isolated scope.
Basically, I shifted the html to a template called report. I changed the template html a little bit. Here's the changed code:
<div class="radio-inline" ng-repeat="condition in radioValues">
<label class="radio-inline">
<input type="radio" name="{{section}}-{{part}}" ng-value="{{condition.value}}" ng-model="detail.report[section][part]">
{{condition.text}}
</label>
</div>
Then created a directive like so:
app.module('').directive('rating', function(){
return {
scope : {
report: "=",
detail: "=",
radios: "="
},
restrict : 'E',
templateUrl : '../view/rating.html',
link : function($scope, iElm, iAttrs, controller) {}
};
});
And in the html I simply call:
<rating report="report" radios="radios" detail="detail"></rating>
So I was able to access the detail object in the parent scope by passing it to the template. This allowed me to modify the parent scope's model directly.
I have these objects right here that I will use to save data from a form, and later send it to an api as JSON :
$scope.price = {}
$scope.item = {"price":$scope.price, };
I also have these field which will be used to dynamically generate inputs on a html page:
$scope.fields = [
{
name: $scope.item.title,
title: 'Title',
type: {
view: 'input'
}
},
{
name: $scope.price.regular,
title: 'Regualar Price',
type: {
view: 'input'
}
}
];
Now in order to generate the form I use this code:
<div class="form-group" ng-repeat="field in fields">
<label>{{ field.title }}:</label>
<span ng-switch on="field.type.view">
<span ng-switch-when="input">
<input
ng-model=field.name
type="text"
/>
</span>
</span>
</div>
And with this code, it is not assigning the values in the input to the objects. Is there a way to do it? I know I can do it this way:
ng-model="item[field.name]"
But that limits me to only one level of the object. I want to be able to bind nested objects. And I just can't seem to figure it out. Thank You!
I have a data model persons which takes the following form:
personsInfo = {
name: Adam
dob: 31-FEB-1985
docs: [
{
docType: Drivers License,
number: 121212,
selected: false
id: 1
},
{
selected: true,
docType: None
},
{
docType: State ID,
number: 132345,
selected: false,
id: 2
}
]
}
In my markup I have defined the following to dynamically generate radio buttons.
<div ng-repeat="personDoc in personsInfo.docs">
<input type="radio" name="personDocs" ng-model="personDoc.selected" value=""/>
{{personDoc.docType}} <span ng-hide="personDoc.docType === 'None'">Number: {{personDoc.number}}</span>
</div>
I want to be able to check the documents which have selected as true on page load, and then depending on what the user selects save the selected flag in my personsInfo model.
My intent here is to send the personsInfo model back to another page.
If somebody could point me to a working fiddle it would be greatly appreciated!
Thanks!
You're almost there just missing the binding to show which document is selected. We'll add an object to the scope to represent the selected item, then bind the forms to that model.
JS
app.controller('...', function($scope) {
$scope.personInfo = { ... };
$scope.selectedDoc = {};
$scope.$watch('personInfo',function() {
$scope.selectedDoc = $scope.personInfo.docs[0];
});
});
HTML
<div ng-repeat='doc in personInfo.docs'>
<input type='radio' ng-model='selectedDoc' value='doc' /> {{doc.docType}}
</div>
<form>
<input type='text' ng-model='selectedDoc.number' />
...
</form>
I am creating a dynamic form using json and trying to use jquery-validation plugin to add validation rules to input fields.
Json structure and helper method is :
var fields = [{
label: "Name",
type: {name: 'STRING'},
validationRules: {
required: true,
maxlength: 100,
minlength: 3
}
},{
label: "Currency",
type: {name: 'CHECKBOX'},
defaultValue: ['USD', 'INR'],
validationRules: {
required: true
},
sourceCollection: 'oNLFfi4L3zgNLhScv',
}] ;
Template.eventCreate.helpers({
fields: function(){
console.log("calling fields");
fields.forEach(function(field, index){
field.sourceCollectionData = StaticLists.find({_id: field.sourceCollection});
});
return fields;
}
});
Template looks like:
<template name="eventCreate">
<form id="newEventForm" class="form-horizontal">
{{#each fields}}
<div class="form-group">
<label class="col-xs-2 control-label">{{label}}</label>
<div class="col-xs-6">
{{#if equals type.name 'STRING'}}
<input name="{{label}}" id="{{label}}" class="form-control" placeholder="Enter {{label}}" value="{{defaultValue}}" />
{{/if}}
{{#if equals type.name 'CHECKBOX'}}
{{#each sourceCollectionData}}
{{#if isActive}}
<div class="col-xs-2 checkbox">
<label class="checkbox-inline">
<input type="checkbox" name="{{../label}}" id="{{../label}}" value="{{name}}" {{checked ../defaultValue}}> {{name}}
</label>
</div>
{{/if}}
{{/each}}
{{/if}}
</div>
</div>
{{/each}}
</form>
</template>
Now I am trying to add validation rule in rendered method:
Template.eventCreate.rendered = function(){
$('#newEventForm').validate({
....
});
fields.forEach(function(field, index){
if(field.validationRules){
$('#'+field.label).rules('add', field.validationRules);
}
});
}
It works in case of input text but throws exception for checkbox because checkbox DOM is still not laid out and there is no element with id "Currency".
I assume in meteor blaze rendered is called once only when DOM rendering is complete. Though it is called once here but before DOM rendering completes.
EDIT
Earlier I was hardcoding the JSON in js file itself and now I am retrieving it from mongo.
But its seems that rendered callback is invoked only first time and not invoked every time mongo collection changes(which in turn updates the DOM)
Template.eventCreate.helpers({
fields: function(){
var template = EventTemplate.findOne({name: 'Job Template'});
console.log("template", template);
if(template){
Session.set("template", template);
template.fields.forEach(function(field, index){
field.sourceCollectionData = StaticLists.find({_id: field.sourceCollection});
});
return template.fields;
}
}
});
Template.eventCreate.rendered = function(){
$('#newEventForm').validate({
...
...
console.log("rendering main template");
addValidationRules(Session.get('template'));
}
Now the console output is something like:
template undefined event_create.js?bfeba9389401dac358bc313bd9d4205818e356a1:52
rendering main template event_create.js?bfeba9389401dac358bc313bd9d4205818e356a1:98
template Object {_id: "iFDndmjavtFN8AdGQ", name: "Job Template", description: "Job Template", fields: Array[13]}
which shows that (and I tried it even with a break point in js script) that just when script loads template is undefined and no input field is rendered on front end but rendered callback is invoked. Now later when template gets populated with data, input fields are rendered but callback is not invoked again.
The recommended pattern is to put the contents of your {{#each}} blocks in separate templates and tap into the rendered event of those templates.
By the way, Meteor.defer is no longer required.