I have angular application which uses directives.
In the directive I have template that defines pop up modal.
Basically, it's very simple app that shows a list of book authors, and in the list there is an Edit button that opens modal box.
If I open the modal for editing the book author, and just close it, without editing the author - there is no problem.
But if I open the modal, and type something in the author input, and close it, the model is stuck with the current input value for the whole time, so if I open another author for editing, the model will not be updated.
My question is: why is this happening, and how to fix it ?
HTML
<div ng-controller="MyCtrl">
<table class="table table-hover">
<tr>
<td><b>Publisher</b></td>
<td><b>Edit Publisher</b></td>
</tr>
<tr ng-repeat="book in books">
<td>
{{book.Author}}
</td>
<td>
<span ng-click="toggleModal(book)" class="btn btn-primary">Edit</span>
</td>
</tr>
</table>
<modal-dialog info='modalShown' show='modalShown' width='600px' height='60%'>
<div ng-show="divBook">
<input type="text" name="bookAuthor" ng-model="bookAuthor" />
</div>
</modal-dialog>
</div>
Angular
var myApp = angular.module('myApp',[]);
myApp.controller("MyCtrl", function($scope){
$scope.books = [{Author:"Jon Skeet"},{Author:"John Papa"},{Author:"Scott Hanselman"}];
$scope.modalShown = false;
$scope.toggleModal = function (book) {
$scope.bookAuthor = book.Author;
$scope.modalShown = !$scope.modalShown;
$scope.divBook = true;
};
});
myApp.directive('modalDialog', function () {
return {
restrict: 'E',
template: "<div class='ng-modal' ng-show='show'>"
+"<div class='ng-modal-overlay' ng-click='hideModal()'>"
+"</div>"
+"<div class='ng-modal-dialog' ng-style='dialogStyle'>"
+"<div class='ng-modal-close' ng-click='hideModal()'>X</div>"
+"<div class='ng-modal-dialog-content' ng-transclude>"
+"</div>"
+"</div>"
+"div>",
replace: true,
scope: {
show: '=info'
},
transclude: true,
link: function (scope, element, attrs) {
//scope.apply();
scope.dialogStyle = {};
if (attrs.width)
scope.dialogStyle.width = attrs.width;
if (attrs.height)
scope.dialogStyle.height = attrs.height;
scope.hideModal = function () {
scope.show = false;
};
}
};
});
So, the test case will be:
Click Edit -> change the value -> close the modal
Click Edit on another author.
JSFiddle: http://jsfiddle.net/HB7LU/17694/
The model value is changing, however you are creating a new variable and not modifying the original element of the array.
You can change that by putting a pointer of the array object in a scope variable
$scope.toggleModal = function (book) {
$scope.book = book;
$scope.modalShown = !$scope.modalShown;
$scope.divBook = true;
};
then pointing the ng-model to the .Author property of the object.
<input type="text" name="bookAuthor" ng-model="book.Author" />
See modified JSFiddle: http://jsfiddle.net/9a2jcc9u/1/
I have changed your JS fiddle, if you want to change name and it automatically changes in grid than remove angular.copy(book) and directly assign book. you can see your jsfiddle here jsfiddle
myApp.controller("MyCtrl", function($scope){
$scope.books = [{Author:"Jon Skeet"},{Author:"John Papa"},{Author:"Scott Hanselman"}];
$scope.modalShown = false;
$scope.toggleModal = function (book) {
$scope.book = angular.copy(book);
$scope.modalShown = !$scope.modalShown;
$scope.divBook = true;
};
});
your modal dialog
<modal-dialog info='modalShown' show='modalShown' width='600px' height='60%'>
<div ng-show="divBook">
<input type="text" name="bookAuthor" ng-model="book.Author" />
</div>
</modal-dialog>
try something like this
myApp.controller('MyCtrl', ['$scope',function($scope) {
//your code
}]);
Related
I have this problem in my app, when I use slice to remove the user from the list. However, it does not remove from the list. I am getting the user with a API url call. But for some reason, it does not remove the user from the list. Please, have a look at my code. If, you guys want the full code, I have put it in my github. I hope we both can solve this. Thank you in advance.
Here is my code:
<div ng-controller="MyCtrl">
<div ng-repeat="person in userInfo.lawyers | filter : {id: lawyerId}">
<a class="back" href="#/lawyer">Back</a>
<button type="button" class="edit" ng-show="inactive" ng-click="inactive = !inactive">
Edit
</button>
<button type="submit" class="submit" ng-show="!inactive" ng-click="inactive = !inactive">Save</button>
<a class="delete" ng-click="confirmClick(); confirmedAction(person);" confirm-click>Confirm</a>
<div class="people-view">
<h2 class="name">{{person.firstName}}</h2>
<h2 class="name">{{person.lastName}}</h2>
<span class="title">{{person.email}}</span>
<span class="date">{{person.website}} </span>
</div>
<div class="list-view">
<form>
<fieldset ng-disabled="inactive">
<legend>Basic Info</legend>
<b>First Name:</b>
<input type="text" ng-model="person.firstName">
<br>
<b>Last Name:</b>
<input type="text" ng-model="person.lastName">
<br>
<b>Email:</b>
<input type="email" ng-model="person.email">
<br>
<b>Website:</b>
<input type="text" ng-model="person.website">
<br>
</fieldset>
</form>
</div>
</div>
</div>
App.js
var app = angular.module("Portal", ['ngRoute', 'ui.bootstrap' ]);
app.controller('MyCtrl', function($scope, $window) {
$scope.inactive = true;
$scope.confirmedAction = function (lawyer) {
$scope.$apply(function () {
var index = $scope.userInfo.lawyers.indexOf(lawyer);
console.log($scope.userInfo.lawyers);
$scope.userInfo.lawyers.splice(index, 1);
console.log($scope.userInfo.lawyers);
$window.location.href = '#/lawyer';
});
};
});
app.directive('confirmClick', ['$q', 'dialogModal', function($q, dialogModal) {
return {
link: function (scope, element, attrs) {
// ngClick won't wait for our modal confirmation window to resolve,
// so we will grab the other values in the ngClick attribute, which
// will continue after the modal resolves.
// modify the confirmClick() action so we don't perform it again
// looks for either confirmClick() or confirmClick('are you sure?')
var ngClick = attrs.ngClick.replace('confirmClick()', 'true')
.replace('confirmClick(', 'confirmClick(true,');
// setup a confirmation action on the scope
scope.confirmClick = function(msg) {
// if the msg was set to true, then return it (this is a workaround to make our dialog work)
if (msg===true) {
return true;
}
// msg can be passed directly to confirmClick('Are you sure you want to confirm?')
// in ng-click
// or through the confirm-click attribute on the
// <a confirm-click="Are you sure you want to confirm?"></a>
msg = msg || attrs.confirmClick || 'Are you sure you want to confirm?';
// open a dialog modal, and then continue ngClick actions if it's confirmed
dialogModal(msg).result.then(function() {
scope.$eval(ngClick);
});
// return false to stop the current ng-click flow and wait for our modal answer
return false;
};
}
}
}])
/*
Modal confirmation dialog window with the UI Bootstrap Modal service.
This is a basic modal that can display a message with yes or no buttons.
It returns a promise that is resolved or rejected based on yes/no clicks.
The following settings can be passed:
message the message to pass to the modal body
title (optional) title for modal window
okButton text for YES button. set false to not include button
cancelButton text for NO button. ste false to not include button
*/
.service('dialogModal', ['$modal', function($modal) {
return function (message, title, okButton, cancelButton) {
// setup default values for buttons
// if a button value is set to false, then that button won't be included
cancelButton = cancelButton===false ? false : (cancelButton || 'No');
okButton = okButton ===false ? false : (okButton || 'Yes');
// setup the Controller to watch the click
var ModalInstanceCtrl = function ($scope, $modalInstance, settings) {
// add settings to scope
angular.extend($scope, settings);
// yes button clicked
$scope.ok = function () {
// alert("Lawyer is confirmed");
$modalInstance.close(true);
};
// no button clicked
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
};
// open modal and return the instance (which will resolve the promise on ok/cancel clicks)
var modalInstance = $modal.open({
template: '<div class="dialog-modal"> \
<div class="modal-header" ng-show="modalTitle"> \
<h3 class="modal-title">{{modalTitle}}</h3> \
</div> \
<div class="modal-body">{{modalBody}}</div> \
<div class="modal-footer"> \
<button class="btn btn-primary" ng-click="ok()" ng-show="okButton">{{okButton}}</button> \
<button class="btn btn-warning" ng-click="cancel()" ng-show="cancelButton">{{cancelButton}}</button> \
</div> \
</div>',
controller: ModalInstanceCtrl,
resolve: {
settings: function() {
return {
modalTitle: title,
modalBody: message,
okButton: okButton,
cancelButton: cancelButton
};
}
}
});
// return the modal instance
return modalInstance;
}
}])
app.config(function ($routeProvider) {
$routeProvider
.when("/lawyer", {
controller: "HomeController",
templateUrl: "partials/home.html"
})
.when("/lawyer/:id", {
controller: "LawyerController",
templateUrl: "partials/about.html"
})
.otherwise({
redirectTo: '/lawyer'
});
});
But the element is getting deleted from list right?
If yes then, Try this :
$scope.confirmedAction = function (lawyer) {
$scope.$apply(function () {
var index = $scope.userInfo.lawyers.indexOf(lawyer);
console.log($scope.userInfo.lawyers);
$scope.userInfo.lawyers.splice(index, 1);
console.log($scope.userInfo.lawyers);
$window.location.href = '#/lawyer';
});
});
Or
$scope.confirmedAction = function (lawyer) {
$timeout(function () {
var index = $scope.userInfo.lawyers.indexOf(lawyer);
console.log($scope.userInfo.lawyers);
$scope.userInfo.lawyers.splice(index, 1);
console.log($scope.userInfo.lawyers);
$state.go($state.current, {}, {reload: true});
// $window.location.href = '#/lawyer';
},1100);
});
The problem is that you don't get the index from IndexOf when you using it on an array of objects like you do. Read more about the IndexOf here.
Instead, use map and then use IndexOf on that
Try it like this:
$scope.confirmedAction = function (lawyer) {
var index = $scope.userInfo.lawyers.map(function(e) { return e.id; }).indexOf(lawyer.id);
$scope.userInfo.lawyers.splice(index, 1);
console.log($scope.userInfo.lawyers);
$window.location.href = '#/lawyer';
};
And also, the controller changes will by default be detected by the digest loop of angular so no need to use $scope.$apply.
Also, simplifyed your plunker with the basic get array list and remove function. Use that to build your way forvered
https://plnkr.co/edit/w6NuVLcqzc5Rjs7L6Cox?p=preview
I have a simple angular page:
<div ng-controller="JoinGameController">
<table>
<tr ng-repeat="game in gameList">
<td>{{game}}</td>
<td> </td>
<td>select</td>
</tr>
</table>
<br/>
Selected game: {{selectedGameId}}
<br/>
Clear
<div ng-if="isGameSelected">
<p>To join this game put your name in the box and click the button</p>
<input type="text" ng-model="playerName">
<button ng-click="joinExistingGame()">Join Game</button>
<br/>
<p ng-if="playerAdded">{{addPlayerResponse}}</p>
</div>
</div>
my problem is that the player input box: ng-model="playerName">
doesn't provide the value when the button is clicked (binding doesn't work).
However it will work when I move it above the DIV element it belongs to.
Here is the controller of that page:
'use strict';
angular.module('pokerApp.joinGame', ['ngRoute'])
.config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/joinGame', {
templateUrl: 'joinGame/joinGame.html',
controller: 'JoinGameController'
});
}])
.controller('JoinGameController', ['$http', '$scope', function ($http, $scope) {
$http.get('http://localhost:8080/PokerGame-REST/allGames').success(function (response) {
$scope.gameList = response;
});
$scope.selectedGameId = null;
$scope.isGameSelected = false;
$scope.playerName = '';
$scope.playerAdded = false;
function selectGame(game) {
$scope.selectedGameId = game;
$scope.isGameSelected = true;
}
function clearSelection() {
$scope.selectedGameId = null;
$scope.isGameSelected = false;
}
function joinExistingGame() {
$http.get('http://localhost:8080/PokerGame-REST/addHand', {
params: {
gameId: $scope.selectedGameId,
name: $scope.playerName
}
}).success(function (response) {
$scope.playerAdded = true;
$scope.addPlayerResponse = response.addHand;
});
}
$scope.selectGame = selectGame;
$scope.clearSelection = clearSelection;
$scope.joinExistingGame = joinExistingGame;
}]);
What is the catch here?
You issue is that ng-if creates child scope so you cannot access your property inside the div with ng-if attribute.
you may try $parent.playerName
issue with using ng-if directive, that create own scope. So playerName added in this scope of ng-if directive.
Just create in main scope variable, like: player={}, and use it
<input type="text" ng-model="player.playerName">
I'm trying to load data into modal using AngularJS. I did the load the data into a list of "cards" and it works fine. But, to each card, I need to open a details modal and to load the rest of the data within it. Follow my code:
//Part of index.html
<body ng-controller="CardsController">
<div class="container">
<div class="cards" ng-repeat="card in cards">
<h3>{{card.user}}</h3>
<button type="button" name="play" title="play" ng-click="toggleModal(card)">Play</button>
</div>
</div>
<my-modal show='modalShown' width='250px' height='40%'>
<h3>{{card.user}}</h3> <-- here is my problem!
</my-modal>
// js/controllers/cards-controller.js
angular.module('angstudy').controller('CardsController', function($scope, $http){
$scope.cards = [];
$http.get('http://localhost:3000/api/persons')
.success(function(retorno){
console.log(retorno);
$scope.cards = retorno;
})
.error(function(erro) {
console.log(erro);
});
$scope.modalShown = false;
$scope.toggleModal = function(card) {
$scope.modalShown = !$scope.modalShown;
};
});
// js/directives/modal-dialog.js
angular.module('modalDialog', [])
.directive('myModal', function() {
var ddo = {};
ddo.restrict = "E";
ddo.transclude = true;
ddo.scope = {
user: '#user',
show: '='
};
ddo.link = function(scope, element, attrs) {
scope.dialogStyle = {};
if (attrs.width)
scope.dialogStyle.width = attrs.width;
if (attrs.height)
scope.dialogStyle.height = attrs.height;
scope.hideModal = function() {
scope.show = false;
};
};
ddo.templateUrl = 'js/directives/modal-dialog.html';
return ddo;
});
// js/directives/modal-dialog.html (template for the directive)
<div class='ng-modal' ng-show='show'>
<div class='ng-modal-overlay' ng-click='hideModal()'></div>
<div class='ng-modal-dialog' ng-style='dialogStyle'>
<div class='ng-modal-close' ng-click='hideModal()'>X</div>
<div class='ng-modal-dialog-content'></div>
</div>
</div>
// js/main.js
angular.module('angstudy', ['modalDialog']);
The cards are been displayed normally and the modal opens, but does not display the AE values within the modal (in this case, I'm just testing the value "user", but the json has more data). If I insert just a static value, it displays...
I would've done it by keeping the modal html in a separate file and using a separate controller and I would pass the data to the Modal controller with the resolve keyword.
But since you are using the same controller on both the templates. You can keep a separate variable called $scope.SelectedCard to achieve the functionality.
In your toggleModal method you can assign the card as:
$scope.toggleModal = function(card) {
$scope.modalShown = !$scope.modalShown;
$scope.selectedCard = card;
};
and in the modal you can change to:
<my-modal show='modalShown' width='250px' height='40%'>
<h3>{{selectedCard.user}}</h3> <-- problem solved
</my-modal>
I have this code:
app.js
var app = angular.module('plunker', []);
app.controller('MainCtrl', function() {
var vm = this;
vm.unit = '';
vm.unitForm = "unitForm";
vm.unitInput = "unitInput";
vm.amount = '';
vm.amountForm = "amountForm";
vm.amountInput = "amountInput";
vm.errorText = "Error";
vm.pattern = new RegExp("^[0-9]*$");
vm.calculate = calculate;
function calculate(first, second){
var firstFloat = parseFloat(first.replace(",","."));
var secondFloat = parseFloat(second.replace(",","."));
var total = (firstFloat * secondFloat).toFixed(2);
console.log('calculating', firstFloat, secondFloat, total)
return total.toString().replace(".",",");
}
});
app.directive('textInput', function textInput() {
return {
restrict: 'E',
templateUrl: 'text-input.html',
scope: {
myClass: "#",
value: '=?value',
formName: '=',
inputName: '=',
pattern: '=',
errorText: '=',
change: "&"
}
};
});
text-input.html
<form name="formName">
<input type="text"
name="inputName"
ng-model="value"
ng-class="myClass"
ng-pattern="pattern"
ng-trim="false"
ng-change="change()"
/>
<div data-ng-show="formName.inputName.$error.pattern">
{{errorText}}
</div>
</form>
index.html
<text-input my-class="input"
form-name="vm.unitForm"
input-name="vm.unitInput"
pattern="vm.pattern"
error-text="vm.errorText"
value="vm.unit"
change="vm.total = vm.calculate(vm.amount, vm.unit)"
></text-input>
<br>
<text-input my-class="input"
form-name="vm.amountForm"
input-name="vm.amountInput"
pattern="vm.pattern"
error-text="vm.errorText"
value="vm.amount"
change="vm.total = vm.calculate(vm.amount, vm.unit)"
></text-input>
vm.unit: {{vm.unit}}
<br>
vm.amount: {{vm.amount}}
<br><br>
Just jusing *:{{vm.unit*vm.amount}}
<br>
Change:
{{vm.total}}
Link to plnkr: http://plnkr.co/edit/DOc0v7o7arS6PhYOHaK0?p=preview
As can be seen, I have a directive that binds a variable to a function.
For some reason I can't figure out, the whole thing lags.
To see what's wrong do this: Enter a value in the top box, i.e 2. Enter a value in the second box, i.e 3.
The models lag in input. To see a change from the function, you need to enter another value. But the function now uses the values before your last input!
So, I am thoroughly confused here. What's going on and why won't this work?
please watch this Plunker
So I working with angular and need to add new input field when all others are filled in (by default on page placed 5 inputs and if all of them are filled automatically add one more input if new input also using will add one more input and etc).
For generate inputs I use ng-repeat and name_list[] for it:
<div collect-input>
<div class="form-group" ng-repeat="(i, name) in name_list track by $index">
<div class="row">
<div class="col-xs-12">
<input class="form-control" type="text" ng-model="data.name_list[i]" add-input/>
</div>
</div>
</div>
Each input have directive attr "add-input" with $watch() method inside. This method method track when $isEmpty parameter had changed.
Then value function pass value of this parameter to listen function.
directive('addInput', ['$compile', '$sce', '$timeout', function ($compile, $sce, $timeout) {
return {
restrict: 'A',
require: ['^collectInput', '?ngModel'],
link: function (scope, element, attrs, ctrl) {
var collectInput = ctrl[0];
var ngModel = ctrl[1];
$timeout(function(){
scope.$watch(
function(){
return ngModel.$isEmpty(ngModel.$modelValue);
},
function(isEmpty){
collectInput.reportInput(ngModel, isEmpty);
}
);
},1000)
}
}
}]);
Then this function call "reportInput()" that placed inside parent directive "collect-input". Main goal of this function is to add new input name to name_list[] for generating via ng-repeat
userApp.directive('collectInput', function() {
return {
restrict: 'A',
controller: function($scope) {
var dirtyCount = 0;
this.reportInput = function(modelValue, isEmpty) {
var count = $scope.name_list.length;
if (isEmpty == false){
dirtyCount ++;
console.log('+1:' + dirtyCount);
}
if (isEmpty == true){
if (dirtyCount <= 0){
dirtyCount = 0;
console.log('0:' + dirtyCount);
}
else if(dirtyCount > 0){
dirtyCount --;
console.log('-1:' + dirtyCount)
}
}
if (count === dirtyCount) {
$scope.name_list.push(modelValue);
//dirtyCount = dirtyCount + 1;
}
console.log('count:' + count);
console.log('dirtyCount:' + dirtyCount);
console.log(modelValue)
}
},
link: function(scope, element, attrs) {
}}});
So when I filled 5 default inputs everything is good after it appears new input but it is all in my IDE it work perfect if I add only one symbol for 5+ label (in plunker in some reason it not work) but when I add or delete something more code logic crash. It's hard to explain. I hope Plunker code more clarify this.
Not tested, and could be optimized, but here's my idea:
HTML :
<div class="form-group" ng-repeat="name in name_list">
<div class="row">
<div class="col-xs-12">
<input class="form-control" ng-model="name"/>
</div>
</div>
</div>
JS :
//watch any modification in the list of names
$scope.$watchCollection('data.name_list', function (list) {
//is there an empty name in the list?
if (!list.filter(function (name) { return !name; }).length) {
//if not, let's add one.
data.name_list.push('');
//and that will automatically add an input to the html
}
});
I don't see the point of a directive.