How to get dynamic ng-model from ng-repeat in javascript? - angularjs

I'm developoing a web app and stuck here:
Part of the HTML:
<div class="input-group">
<select name="select" class="form-control input-group-select" ng-options="key as key for (key , value) in pos" ng-model="word.pos" ng-change="addPos()">
<option value="">Choose a POS</option>
</select>
<span class="sort"><i class="fa fa-sort"></i></span>
</div>
<ul class="listGroup" ng-show="_pos.length > 0">
<li class="list" ng-repeat="item in _pos track by $index">
<span>
{{item.pos}}
<span class="btn btn-danger btn-xs" ng-click="delPos($index)">
<span class="fa fa-close"></span>
</span>
</span>
<!-- I wanna add the input which can add more list item to the item.pos-->
<div class="input-group">
<a class="input-group-addon add" ng-class=" word.newWordExp ? 'active' : ''" ng-click="addItemOne()"><span class="fa fa-plus"></span></a>
<input type="text" class="form-control exp" autocomplete="off" placeholder="Add explanation" ng-model="word.newWordExp" ng-enter="addExpToPos()">
{{word.newWordExp}}
</div>
</li>
</ul>
Part of the js:
$scope._pos = [];
$scope.addPos = function () {
console.log("You selected something!");
if ($scope.word.pos) {
$scope._pos.push({
pos : $scope.word.pos
});
}
console.dir($scope._pos);
//console.dir($scope.word.newWordExp[posItem]);
};
$scope.delPos = function ($index) {
console.log("You deleted a POS");
$scope._pos.splice($index, 1);
console.dir($scope._pos);
};
$scope.addItemOne = function (index) {
//$scope.itemOne = $scope.newWordExp;
if ($scope.word.newWordExp) {
console.log("TRUE");
$scope._newWordExp.push({
content: $scope.word.newWordExp
});
console.dir($scope._newWordExp);
$scope.word.newWordExp = '';
} else {
console.log("FALSE");
}
};
$scope.deleteItemOne = function ($index) {
$scope._newWordExp.splice($index, 1);
};
So, what am I wannt to do is select one option and append the value to $scope._pos, then display as a list with all of my selection.
And in every list item, add an input filed and add sub list to the $scope._pos value.
n.
explanation 1
explanation 2
explanation 3
adv.
explanation 1
explanation 2
So I don't know how to generate dynamic ng-model and use the value in javascript.
Normaly should like ng-model="word.newExplanation[item]" in HTML, but in javascript, $scope.word.newExplanation[item] said "item is not defined".
can any one help?

If I've understood it correclty you could do it like this:
Store your lists in an array of object this.lists.
The first object in the explanation array is initialized with empty strings so ng-repeat will render the first explanation form.
Then loop over it with ng-repeat. There you can also add dynamically the adding form for your explanation items.
You can also create append/delete/edit buttons inside the nested ng-repeat of your explanation array. Append & delete is already added in the demo.
Please find the demo below or in this jsfiddle.
angular.module('demoApp', [])
.controller('appController', AppController);
function AppController($filter) {
var vm = this,
explainTmpl = {
name: '',
text: ''
},
findInList = function (explain) {
return $filter('filter')(vm.lists, {
explanations: explain
})[0];
};
this.options = [{
name: 'option1',
value: 0
}, {
name: 'option2',
value: 1
}, {
name: 'option3',
value: 2
}];
this.lists = [];
this.selectedOption = this.options[0];
this.addList = function (name, option) {
var list = $filter('filter')(vm.lists, {
name: name
}); // is it in list?
console.log(name, option, list, list.length == 0);
//vm.lists
if (!list.length) {
vm.lists.push({
name: name,
option: option,
explanations: [angular.copy(explainTmpl)]
});
}
};
this.append = function (explain) {
console.log(explain, $filter('filter')(vm.lists, {
explanations: explain
}));
var currentList = findInList(explain);
currentList.explanations.push(angular.copy(explainTmpl));
}
this.delete = function (explain) {
console.log(explain);
var currentList = findInList(explain),
index = currentList.explanations.indexOf(explain);
if (index == 0 && currentList.explanations.length == 1) {
// show alert later, can't delete first element if size == 1
return;
}
currentList.explanations.splice(index, 1);
};
}
AppController.$inject = ['$filter'];
<link href="http://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet"/>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="demoApp" ng-controller="appController as ctrl">
<select ng-model="ctrl.selectedOption" ng-options="option.name for option in ctrl.options"></select>
<input ng-model="ctrl.listName" placeholder="add list name" />
<button class="btn btn-default" title="add list" ng-click="ctrl.addList(ctrl.listName, ctrl.selectedOption)"><i class="fa fa-plus"></i>
</button>
<div class="list-group">Debug output - current lists:<pre>{{ctrl.lists|json:2}}</pre>
<div class="list-group-item" ng-repeat="list in ctrl.lists">
<h2>Explanations of list - {{list.name}}</h2>
<h3>Selected option is: {{ctrl.selectedOption}}</h3>
<div class="list-group" ng-repeat="explain in list.explanations">
<div class="list-group-item">
<p class="alert" ng-if="!explain.title">No explanation here yet.</p>
<div class="well" ng-if="explain.title || explain.text">
<h4>
{{explain.title}}
</h4>
<p>{{explain.text}}</p>
</div>Title:
<input ng-model="explain.title" placeholder="add title" />Text:
<input ng-model="explain.text" placeholder="enter text" />
<button class="btn btn-primary" ng-click="ctrl.append(explain)">Append</button>
<button class="btn btn-primary" ng-click="ctrl.delete(explain)">Delete</button>
</div>
</div>
</div>
</div>
</div>

Related

Storing dynamic form field as an array in angular

question.component.ts
ngOnInit() {
this.challengeId = this.route.snapshot.paramMap.get('challengeId');
// this.selectedNoOfAttempt = this.noofattempts[1];
this.myForm = this._fb.group({
question: ['', [Validators.required]],
correctAnswers: this._fb.array([
this.initCorrectAnswer(),
]),
selectedNoOfAttempt: ['', [Validators.required]]
});
}
initCorrectAnswer() {
return this._fb.group({
correctAnswers: ['', Validators.required],
});
}
addAnswer() {
const control = <FormArray>this.myForm.controls['correctAnswers'];
control.push(this.initCorrectAnswer());
}
removeAnswer(i: number) {
const control = <FormArray>this.myForm.controls['correctAnswers'];
control.removeAt(i);
}
question.component.html
<div formArrayName="correctAnswers">
<div *ngFor="let correctAnswers of myForm.controls.correctAnswers.controls; let i=index" class="panel panel-default">
<div class="panel-heading">
<span>Answer {{i + 1}}</span>
<span class="glyphicon glyphicon-remove pull-right" *ngIf="myForm.controls.correctAnswers.controls.length > 1" (click)="removeAnswer(i)">Remove</span>
</div>
<div class="panel-body" [formGroupName]="i">
<div class="form-group col-xs-3">
<input type="text" class="form-control" formControlName="correctAnswers">
<small [hidden]="myForm.controls.correctAnswers.controls[i].controls.correctAnswers.valid" class="text-danger">
Answer is required
</small>
</div>
</div>
</div>
</div>
<div class="margin-20">
<a (click)="addAnswer()" style="cursor: default">
Add another answer +
</a>
</div>
The code above works as it is intended, dynamically insert more fields if needed, and also remove. It also able to return all the dynamic fields, however it is storing the data as
correctAnswers (array)
[0] (object)
correctAnswers: "test" (string)
[1] (object)
correctAnswers: "abc" (string)
I would like to store the data as follow
correctAnswers (array)
[0] "test" (string)
[1] "abc" (string)
In your initCorrectAnswer() function you are adding form group with child form control to your form array, that is why you see it as array of objects. Instead of this, you should add form control:
initCorrectAnswer() {
return this._fb.control('', Validators.required);
}
I also added correctAnswers getter for convenient use in HTML:
get correctAnswers(): FormArray {
return this.myForm.get('correctAnswers') as FormArray;
}
updated add and remove functions:
addAnswer() {
this.correctAnswers.push(this.initCorrectAnswer());
}
removeAnswer(i: number) {
this.correctAnswers.removeAt(i);
}
here is HTML:
<form [formGroup]="myForm">
<div formArrayName="correctAnswers">
<div class="panel panel-default"
*ngFor="let correctAnswer of correctAnswers.controls; index as i">
<div class="panel-heading">
<span>Answer {{i + 1}}</span>
<span class="glyphicon glyphicon-remove pull-right"
*ngIf="correctAnswers.controls.length > 1"
(click)="removeAnswer(i)">Remove</span>
</div>
<div class="panel-body">
<div class="form-group col-xs-3">
<input type="text" class="form-control" [formControlName]="i">
<small [hidden]="correctAnswer.valid" class="text-danger">
Answer is required
</small>
</div>
</div>
</div>
</div>
</form>
StackBlitz: https://stackblitz.com/edit/angular-643srq?file=app%2Fapp.component.html
Official reference
There is no way for you to achieve it through Angular's reactive form, because every form group that is repeated (via your *ngFor) is an object. Note that your form group name is actually your *ngFor index: formGroupName = i; and that your form control is correctAnswers - that corresponds to your object property with name correctAnswers as you have expected it.
You can however, do some post processing though. Simply use an array map() to flatten your object into arrays:
var flattenedArray = this.myForm.correctAnswers.map(x=>x.correctAnswers);

ng-model into ng-repeat Angularjs

I'm asking if is possible to do something as that in angular
<div ng-app="app">
<div ng-controller="mainController">
<ul ng-repeat="movie in movies |searchFilter:Filter.genre | searchFilter:Filter.name |searchFilter:Filter.pic ">
<li>{{movie.name}}</li>
</ul>
<h2>genre</h2>
<div>
<label>Comedy </label><input type="checkbox" ng-model="Filter.genre.Comedy" ng-true-value="Comedy" data-ng-false-value=''/><br/>
</div>
<h2>PIC</h2>
<label>aa</label><input type="checkbox" ng-model="Filter.pic.aa" ng-true-value="ciao" data-ng-false-value=''/><br/>
<h2>Name</h2>
<label>Shrek</label><input type="checkbox" ng-model="Filter.name.Shrek" ng-true-value="The God" data-ng-false-value=''/><br/>
</div>
</div>
i'm creating a checkbox for filter on different fields (size,name,genre)
ill have a list of avaible sizes,names and genres .
The issue is on ng-model and i tried to write it as "Filter.genre.genre.name" or
"Filter["genre"+genre.name]" and also "Filter.genre[genre.name]" but still not work .
the js.file is
var app =angular.module('app', []);
app.controller('mainController', function($scope) {
$scope.movies = [{name:'Shrek', genre:'Comedy',pic:"cc"},
{name:'Die Hard', genre:'Comedy',pic:"aa"},
{name:'The Godfather', genre:'Drama',pic:"ciao"},
{name:'The Godher', genre:'Comedy',pic:"lel"}];
$scope.genres = [{name:"Comedy"},{name:"Action"},{name:"Drama"}];
});
app.filter('searchFilter',function($filter) {
return function(items,searchfilter) {
var isSearchFilterEmpty = true;
//searchfilter darf nicht leer sein
angular.forEach(searchfilter, function(searchstring) {
if(searchstring !=null && searchstring !=""){
isSearchFilterEmpty= false;
}
});
if(!isSearchFilterEmpty){
var result = [];
angular.forEach(items, function(item) {
var isFound = false;
angular.forEach(item, function(term,key) {
if(term != null && !isFound){
term = term.toLowerCase();
angular.forEach(searchfilter, function(searchstring) {
searchstring = searchstring.toLowerCase();
if(searchstring !="" && term.indexOf(searchstring) !=-1 && !isFound){
result.push(item);
isFound = true;
// console.log(key,term);
}
});
}
});
});
return result;
}else{
return items;
}
}
});
if i make 3 different labels for the field Comedy, Action and Drama with ng-models called as
ng-model="Filter.genre.Comedy" ; ng-model="Filter.genre.Action" and ng-model="Filter.genre.Drama"
it work but it doesnt work if i try to write it into ng-repeat . I hope to have been clearer
In this sample i try to handle your question by change the Model of your page.
we have:
list of movies array => $scope.movies = []
dynamic filters array => $scope.genres = [], $scope.years = [] or more
our target:
Create a dynamic filters to search in movies
what we do
$scope.filterHandler = function (key, value) {}
Run when user start searching on input or select, this function help us to create a filter as object by sending key and value which result is {key:value}
$scope.searchTypeHandler = function (type, dependTo) {}
Run when our filters has some array for example genre has genres as dropdown select, this function help us to return the array which depend to the filter.
var app = angular.module("app", []);
app.controller("ctrl", [
"$scope",
function($scope) {
//your options
$scope.movies = [{
name: 'Shrek',
genre: 'Comedy',
year: 2000
},
{
name: 'Die Hard',
genre: 'Action',
year: 2000
},
{
name: 'The Godfather',
genre: 'Drama',
year: 2015
},
{
name: 'The Godher',
genre: 'Comedy',
year: 2017
}
];
$scope.genres = [{
name: "Comedy"
},
{
name: "Action"
},
{
name: "Drama"
}
];
$scope.years = [{
name: 2000
},
{
name: 2015
},
{
name: 2017
}
];
//
$scope.filter = {}
$scope.filterHandler = function(key, value) {
var object = {};
object[key] = value;
$scope.filter["find"] = object;
};
$scope.searchTypeHandler = function(type, dependTo) {
$scope.filter = {};
$scope.filter.searchType = type;
$scope.filter.options = undefined;
if (dependTo != null) {
$scope.filter.options = $scope[dependTo];
}
};
//default
$scope.searchTypeHandler("name");
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<div class="container" ng-app="app" ng-controller="ctrl">
<div class="page-header">
<div class="row">
<div class="col-lg-12">
<div class="col-lg-6 col-md-6 col-sm-6 col-xs-6">
<h4>Movies</h4>
</div>
<div class="col-lg-6 col-md-6 col-sm-6 col-xs-6 pull-right">
<div class="input-group">
<div class="input-group-btn">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
By {{filter.searchType}}
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-left">
<li><a ng-click="searchTypeHandler('genre', 'genres')">Filter By Genre</a></li>
<li><a ng-click="searchTypeHandler('name', null)">Filter By Name</a></li>
<li><a ng-click="searchTypeHandler('year', 'years')">Filter By Year</a></li>
</ul>
</div>
<input ng-hide="filter.options" type="text" class="form-control" ng-model="filter.query" ng-change="filterHandler(filter.searchType, filter.query)">
<select ng-show="filter.options" class="form-control" ng-model="filter.option" ng-change="filterHandler(filter.searchType, filter.option)" ng-options="option.name as option.name for option in filter.options"></select>
</div>
<!-- /input-group -->
</div>
</div>
</div>
</div>
<ul class="list-group">
<li class="list-group-item" ng-repeat="movie in movies | filter: filter.find">
{{movie.name}} - <label class="label label-info">Ggenre: {{movie.genre}}</label> - <label class="label label-default">Year: {{movie.year}}</label>
</li>
</ul>
</div>

AngularJs/Bootstrap - Bind checkbox in list not works properly

I have a roles list that filter a permissions list. The permissions list has a checkbox indicating that it is selected.
Each role has a list of associated permissions. When a role is selected, all available permissions are displayed and those already associated with them will be checked. By clicking on the checkbox of each permission you can associate or desesociate it.
But the select and unselect permission action does not work properly. I can not figure out what the problem. Can someone help me ? Thanks!!!
Code in Plunker
Page code
<div class="panel-body">
<div class="form-group">
<div class="col-md-6">
<div class="list-group">
<a ng-click="selectRole(role)" ng-repeat="role in model.roles" class="list-group-item" ng-class="{active: role.id == model.selectedRole.id}">{{role.name}}</a>
</div>
</div>
<div class="col-md-6">
<div class="list-group">
<a ng-repeat="permission in model.permissions" class="list-group-item">{{permission.name}} <input type="checkbox" class="pull-right" ng-checked="isSelected(permission.id) > -1" ng-click="clickPermission(permission)" ng-class="{active: permission.id == model.selectedPermission.id}" />
</a>
</div>
</div>
</div>
</div>
Controller code
app.controller('PermissionsToRoleController', function($scope) {
$scope.model = {
roles: [],
permissions: [],
selectedRole: null,
selectedPermission: null
};
$scope.selectRole = function(role) {
$scope.model.selectedRole = role;
}
var findRoles = function() {
$scope.model.roles = [{id: 1, name: 'ADMIN', permissions: [{id: 1, name:'MASTER'}] },
{id: 2, name: 'USER', permissions: [] }
];
$scope.model.permissions = [{id: 1, name: 'MASTER'}];
$scope.model.selectedRole = $scope.model.roles[0];
};
$scope.clickPermission = function(permission) {
$scope.model.selectedPermission = permission;
var idx = $scope.isSelected(permission.id);
if (idx > -1) {
$scope.model.selectedRole.permissions.splice(idx, 1);
} else {
$scope.model.selectedRole.permissions.push(permission);
}
};
$scope.isSelected = function(permissionId) {
for (var i = 0; i < $scope.model.selectedRole.permissions.length ; i++) {
if ($scope.model.selectedRole.permissions[i].id === permissionId) {
return i;
}
}
return -1;
}
findRoles();
});
The problem I found being the checkbox, while functions properly, the display won't "uncheck" when you click on it.
This can be fixed by changing the surrounding element to span instead of a
<span ng-repeat="permission in model.permissions" class="list-group-item">{{permission.name}}
<input type="checkbox" class="pull-right" ng-checked="isSelected(permission.id) > -1" ng-click="clickPermission(permission)" ng-class="{active: permission.id == model.selectedPermission.id}" />
</span>
I'm not sure about the reason behind this, though.

How to add value to object from another object by click in Angular

I'm trying to create shopping cart, when clicking on one of the products its add to shoping cart.
So, I build an Object that contains all products, and another array that
will contain all clicked item.
When I'm trying to add the value of correct selected item from the products object i'm getting undefined.
var app = angular.module('myApp',[]); app.controller("myCtrl",function ($scope) {
$scope.products =
[
{ name: 'Apple', price: 5, img: '/Apple.jpg' },
{ name: 'Banana', price: 3.7, img: '/Banana.jpg' },
{ name: 'Grapes', price: 10 , img:'/Grapes.jpg' }
];
$scope.addProduct= function () {
$scope.products.push({name:$scope.nameProduct, price:$scope.priceProduct, tmuna:$scope.imgProduct});
$scope.nameProduct = "";
$scope.priceProduct = "";
$scope.imgProduct = "";
};
$scope.cartList = [{ name: 'Apple'}];
$scope.addToCart = function () {
$scope.cartList.push({name:$scope.nameProduct});
$scope.nameProduct = "";
};
});
<div class="dropdown pull-right">
<button class="btn btn-default dropdown-toggle" type="button" id="menu1" data-toggle="dropdown">
Shopping Cart <span class="glyphicon glyphicon-shopping-cart"></span>
<span class="caret"></span></button>
<ul class="dropdown-menu" role="menu" aria-labelledby="menu1" ng-repeat="product in cartList">
<li role="presentation"><a role="menuitem" tabindex="-1" >{{product.name}}</a></li>
</ul>
</div>
<form >
<p>Product: <input type = "text" ng-model = "nameProduct"></p>
<p>Price: <input type = "number" ng-model = "priceProduct"></p>
<p>Image: <input type = "text" ng-model = "imgProduct"></p>
<input type = "submit" value = "Add" ng-click = "addProduct()">
</form>
</div>
<div class="product" ng-repeat="product in products">
<img ng-src="{{product.img}}" />
<div class="name">{{product.name}}</div>
<div class="price">${{product.price}}</div>
<p class="badge" ng-click="addToCart()">Add to<span class="glyphicon glyphicon-shopping-cart"></span></p>
</div>
When you're calling addToCart() the function uses what's on $scope.nameProduct, but that's an empty string. You cleared it on addProduct() function.
Pass the name of the product you're adding:
ng-click="addToCart(product.name)"
And change your function accordingly:
$scope.addToCart = function (productName) {
$scope.cartList.push({ name: productName });
};
You need to pass the product object in the addToCart function like this:
https://jsfiddle.net/Lyrjp92z/
JS
$scope.addToCart = function(product) {
$scope.cartList.push({
name: product.name
});
};
HTML
<p class="badge" ng-click="addToCart(product)">Add to<span class="glyphicon glyphicon-shopping-cart"></span></p>
Also note, that passing in the product object and not just the string will allow you to also pass in the price, which you can calculate a total from.

ng-repeat with controller for each table row: how do I access x-editable form elements?

I setting up a scenario very similar to the Editable Row example from the x-editable demo site. In this scenario, a there is a simple table with three columns for data and a fourth for edit and delete buttons. A third button outside of the table adds a row to the table. When the form is editable, the data columns become editable (the primary feature of x-editable library). For this demo, the first column becomes a simple text edit and the second two columns become drop lists.
The table is created by having an ng-repeat on a row template. I need to do a few different things that all involve accessing the scope created by the ng-repeat. I need to
detect when the row is editable and when it is not
filter the options for the second drop list when the first drop list changes
In order to try to work with this demo, I've added a controller for the individual row. That has given me some access to the form (name = rowform), but I'm still not able to set a watch on the "make" property. I can't even find what property of the form is changing when the user makes a selection.
How do I set up a watch on the 'make' property?
Page Controller
angular.module('app').controller("quoteBuckingRaterController",
function ($scope, $q, $filter, listService, transactionDataService) {
$scope.equipment = [];
$scope.makes = [];
$scope.models = [];
$scope.showModel = function(equip) {
if(equip.model) {
var selected = $filter('filter')($scope.models, {id: equip.model});
return selected.length ? selected[0].name : 'Not set';
} else {
return 'Not set';
}
};
$scope.showMake = function(equip) {
if (equip.model) {
var selected = $filter('filter')($scope.models, { id: equip.model });
if (selected.length && selected.length > 0) {
if (equip.make != selected[0].make)
equip.make = selected[0].make;
return selected[0].make;
}
else {
return 'Not set';
}
} else {
return 'Not set';
}
};
$scope.checkName = function (data, id) {
if (!data) {
return "Description is required";
}
};
$scope.checkModel = function (data, id) {
if (!data) {
return "Model is required";
}
};
$scope.saveEquipment = function (data, id) {
$scope.inserted = null;
};
$scope.cancelRowEdit = function (data, id) {
$scope.inserted = null;
};
$scope.removeEquipment = function(index) {
$scope.equipment.splice(index, 1);
};
$scope.addEquipment = function() {
$scope.inserted = {
id: $scope.equipment.length+1,
name: '',
make: null,
model: null
};
$scope.equipment.push($scope.inserted);
};
$scope.filterModels = function (make) {
$scope.models = _.where($scope.allModels, function(item) {
return item.make == make;
});
};
//called by another process when page loads
$scope.initialize = function (loaded) {
return $q(function (resolve, reject) {
if (!loaded) {
listService.getEquipmentModels().then(function (data) {
$scope.allModels = data;
$scope.models = data;
//uses underscore.js
$scope.makes = _.chain(data)
.map(function (item) {
var m = {
id: item.make,
name: item.make
};
return m;
})
.uniq()
.value();
resolve();
});
}
});
}
});
Row Controller
angular.module('app').controller("editRowController",
function ($scope) {
$scope.testClick = function () {
alert('button clicked');
};
$scope.make = null;
$scope.$watch('make', function () {
alert('how do I tell when the make has been changed?');
this.$parent.$parent.filterModels(make.id);
});
});
HTML
<div>
<div class="col-md-12" style="margin-bottom: 3px">
<div class="col-md-4 col-md-offset-1" style="padding-top: 6px; padding-left: 0px"><label>Equipment</label></div>
<div class="col-md-offset-10">
<button class="btn btn-primary btn-sm" ng-click="addEquipment()">Add row</button>
</div>
</div>
<div class="col-md-10 col-md-offset-1">
<table class="table table-bordered table-hover table-condensed">
<tr style="font-weight: bold; background-color: lightblue">
<td style="width:35%">Name</td>
<td style="width:20%">Make</td>
<td style="width:20%">Model</td>
<td style="width:25%">Edit</td>
</tr>
<tr ng-repeat="equip in equipment" ng-controller="editRowController">
<td>
<!-- editable equip name (text with validation) -->
<span editable-text="equip.name" e-name="name" e-form="rowform" onbeforesave="checkName($data, equip.id)" e-required>
{{ equip.name || 'empty' }}
</span>
</td>
<td>
<!-- editable make (select-local) -->
<span editable-select="equip.make" e-name="make" e-form="rowform" e-ng-options="s.value as s.name for s in makes">
{{ showMake(equip) }}
</span>
</td>
<td>
<!-- editable model (select-remote) -->
<span editable-select="equip.model" e-name="model" e-form="rowform" e-ng-options="g.id as g.name for g in models" onbeforesave="checkModel($data, equip.id)" e-required>
{{ showModel(equip) }}
</span>
<button type="button" ng-disabled="rowform.$waiting" ng-click="testClick()" class="btn btn-default">
test
</button>
</td>
<td style="white-space: nowrap">
<!-- form -->
<form editable-form name="rowform" onbeforesave="saveEquipment($data, equip.id)" ng-show="rowform.$visible" class="form-buttons form-inline" shown="inserted == equip">
<button type="submit" ng-disabled="rowform.$waiting" class="btn btn-primary">
save
</button>
<button type="button" ng-disabled="rowform.$waiting" ng-click="rowform.$cancel()" class="btn btn-default">
cancel
</button>
</form>
<div class="buttons" ng-show="!rowform.$visible">
<button class="btn btn-primary" ng-click="rowform.$show()">edit</button>
<button class="btn btn-danger" ng-click="removeEquipment($index)">del</button>
</div>
</td>
</tr>
</table>
</div>
</div>
ng-repeat creates a child scope for each row (for each equipment). The scope of the EditRowController is therefore a childScope of the parent quoteBuckingRaterController.
This childScope contains:
all properties of the parent scope (e.g. equipment, makes, models)
the property equip with one value of the equipment array, provided by ng-repeat
any additional scope property that is defined inside the ng-repeat block, e.g. rowform
Therefore you are able to access these properties in the childController editRowController using the $scope variable, e.g.:
$scope.equip.make
$scope.equipment
and inside the ng-repeat element in the html file by using an angular expression, e.g:
{{equip.make}}
{{equipment}}
Now to $scope.$watch: If you provide a string as the first argument, this is an angular expression like in the html file, just without surrounding brackets {{}}. Example for equip.make:
$scope.$watch('equip.make', function (value) {
console.log('equip.make value (on save): ' + value);
});
However, angular-xeditable updates the value of equip.make only when the user saves the row. If you want to watch the user input live, you have to use the $data property in the rowform object, provided by angular-xeditable:
$scope.$watch('rowform.$data.make', function (value) {
console.log('equip.make value (live): ' + value);
}, true);
You can also use ng-change:
<span editable-select="equip.make" e-name="make" e-ng-change="onMakeValueChange($data)" e-form="rowform" e-ng-options="s.value as s.name for s in makes">
JS:
$scope.onMakeValueChange = function(newValue) {
console.log('equip.make value onChange: ' + newValue);
}
That should solve your first question: How to watch the make property.
Your second question, how to detect when the row is editable and when it is not, can be solved by using the onshow / onhide attributes on the form or by watching the $visible property of the rowform object in the scope as documented in the angular-xeditable reference
<form editable-form name="rowform" onshow="setEditable(true)" onhide="setEditable(false)">
$scope.setEditable = function(value) {
console.log('is editable? ' + value);
};
// or
$scope.$watch('rowform.$visible', function(value) {
console.log('is editable? ' + value);
});
You might ask why the rowform object is in the current childScope.
It is created by the <form> tag. See the Angular Reference about the built-in form directive:
Directive that instantiates FormController.
If the name attribute is specified, the form controller is published
onto the current scope under this name.
A working snippet with your example code:
angular.module('app', ["xeditable"]);
angular.module('app').controller("editRowController", function ($scope) {
$scope.testClick = function () {
alert('button clicked');
};
$scope.$watch('equip.make', function (value) {
console.log('equip.make value (after save): ' + value);
});
$scope.$watch('rowform.$data.make', function (value) {
console.log('equip.make value (live): ' + value);
}, true);
// detect if row is editable by using onshow / onhide on form element
$scope.setEditable = function(value) {
console.log('is equip id ' + $scope.equip.id + ' editable? [using onshow / onhide] ' + value);
};
// detect if row is editable by using a watcher on the form property $visible
$scope.$watch('rowform.$visible', function(value) {
console.log('is equip id ' + $scope.equip.id + ' editable [by watching form property]? ' + value);
});
});
angular.module('app').controller("quoteBuckingRaterController", function ($scope, $filter) {
$scope.equipment = [];
$scope.makes = [{value: 1, name: 'Horst'}, {value: 2, name: 'Fritz'}];
$scope.models = [{id: 1, name: 'PC', make: 1}];
$scope.showModel = function(equip) {
if(equip.model) {
var selected = $filter('filter')($scope.models, {id: equip.model});
return selected.length ? selected[0].name : 'Not set';
} else {
return 'Not set';
}
};
$scope.showMake = function(equip) {
if (equip.model) {
var selected = $filter('filter')($scope.models, { id: equip.model });
if (selected.length && selected.length > 0) {
if (equip.make != selected[0].make)
equip.make = selected[0].make;
return selected[0].make;
}
else {
return 'Not set';
}
} else {
return 'Not set';
}
};
$scope.checkName = function (data, id) {
if (!data) {
return "Description is required";
}
};
$scope.checkModel = function (data, id) {
if (!data) {
return "Model is required";
}
};
$scope.saveEquipment = function (data, id) {
$scope.inserted = null;
};
$scope.cancelRowEdit = function (data, id) {
$scope.inserted = null;
};
$scope.removeEquipment = function(index) {
$scope.equipment.splice(index, 1);
};
$scope.addEquipment = function() {
$scope.inserted = {
id: $scope.equipment.length+1,
name: '',
make: null,
model: null
};
$scope.equipment.push($scope.inserted);
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-xeditable/0.1.9/js/xeditable.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/angular-xeditable/0.1.9/css/xeditable.css" rel="stylesheet"/>
<link href="http://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet"/>
<div ng-app="app" ng-controller="quoteBuckingRaterController">
<div class="col-md-12" style="margin-bottom: 3px">
<div class="col-md-4 col-md-offset-1" style="padding-top: 6px; padding-left: 0px"><label>Equipment</label></div>
<div class="col-md-offset-10">
<button class="btn btn-primary btn-sm" ng-click="addEquipment()">Add row</button>
</div>
</div>
<div class="col-md-10 col-md-offset-1">
<table class="table table-bordered table-hover table-condensed">
<tr style="font-weight: bold; background-color: lightblue">
<td style="width:35%">Name</td>
<td style="width:20%">Make</td>
<td style="width:20%">Model</td>
<td style="width:25%">Edit</td>
</tr>
<tr ng-repeat="equip in equipment" ng-controller="editRowController">
<td>
<!-- editable equip name (text with validation) -->
<span editable-text="equip.name" e-name="name" e-form="rowform" onbeforesave="checkName($data, equip.id)" e-required>
{{ equip.name || 'empty' }}
</span>
</td>
<td>
<!-- editable make (select-local) -->
<span editable-select="equip.make" e-name="make" e-form="rowform" e-ng-options="s.value as s.name for s in makes">
{{ showMake(equip) }}
</span>
</td>
<td>
<!-- editable model (select-remote) -->
<span editable-select="equip.model" e-name="model" e-form="rowform" e-ng-options="g.id as g.name for g in models" onbeforesave="checkModel($data, equip.id)" e-required>
{{ showModel(equip) }}
</span>
<button type="button" ng-disabled="rowform.$waiting" ng-click="testClick()" class="btn btn-default">
test
</button>
</td>
<td style="white-space: nowrap">
<!-- form -->
<form editable-form name="rowform" onbeforesave="saveEquipment($data, equip.id)" ng-show="rowform.$visible" class="form-buttons form-inline" shown="inserted == equip" onshow="setEditable(true)" onhide="setEditable(false)">
<button type="submit" ng-disabled="rowform.$waiting" class="btn btn-primary">
save
</button>
<button type="button" ng-disabled="rowform.$waiting" ng-click="rowform.$cancel()" class="btn btn-default">
cancel
</button>
</form>
<div class="buttons" ng-show="!rowform.$visible">
<button class="btn btn-primary" ng-click="rowform.$show()">edit</button>
<button class="btn btn-danger" ng-click="removeEquipment($index)">del</button>
</div>
</td>
</tr>
</table>
</div>
</div>
If you simply want to $watch the make property of equipment, try changing to:
$scope.$watch('equipment.make', function(){(...)})
You could write your own directive for this.
The main advantage is that directives have isolated scope and can have their own controller.
see the directive documentation to know if it's for you.

Resources