Broadcast from directive to controller - angularjs

I am trying to modify a form in a view by means of a button in my directive's template (template is in another file), this is for a basic CRUD where each item has a delete/edit button.
In order to avoid replicating the form I decided to that on edit's click a function would send the item to the controller in questions in order to be updated with the new information.
But I've been having troubles making the connection, so far I tried changing $root, to $rootScope back and forth and using , $broadcast or $emit.
So how can I send the function onChange my item's information based on the template's button click?
Template:
<strong>{{item.type}}</strong> {{item.description}}
<div class="material-switch pull-right">
<button type="button" class="btn btn-warning btn-circle" ng-show="item.editable" ng-click="onChange()">
<span class="glyphicon glyphicon-edit" ></span>
</button>
<button type="button" class="btn btn-danger btn-circle" ng-controller="View1Ctrl" ng-show="item.editable" ng-click="EliminarItem(item)">
<span class="glyphicon glyphicon-minus" ></span>
</button>
<input ng-model="item.isDone"
id="someSwitchOptionDefault{{itemIndex}}"
name="someSwitchOption001{{itemIndex}}"
type="checkbox" />
<label for="someSwitchOptionDefault{{itemIndex}}" class="label-info"></label>
</div>
Directive:
'use strict';
angular.module('myApp.items.directive', [])
.directive('itemSwitch', [ function() {
return {
restrict: 'A',
scope: {
item: '=',
itemIndex: "="
},
templateUrl: 'templates/itemSwitchTemplate.html',
link : function($scope){
$scope.$broadcast('onChange', item);
}
}
}]);
Controller
.controller('View1Ctrl', ['$scope','itemsService',function($scope,itemsService) {
$scope.items = itemsService.getItems();
$scope.classMap = {GROCERIES:"success",CAR:"danger",UNIVERSITY:"warning",PAYMENTS:"info"};
$scope.newItem = {};
$scope.$on('onChange', function(event, args) {
if ($scope.btnEdit) {
$scope.newItem = args;
} else {
$scope.newItem = {};
}
});
$scope.enableEdit = function (item) {
item.editable = true;
};
$scope.disableEdit = function (item) {
item.editable = false;
};
}]);
View
<div class="col-xs-12">
<div ng-model="currentItem" ng-repeat="item in items" item-switch item="item" item-index="$index" class="notice notice-{{classMap[item.type]}}" ng-mouseover="enableEdit(item)" ng-mouseleave="disableEdit(item)">
</div>
<!-- FORMULARIO -->
<form name = "myForm" class="form-horizontal">
<fieldset>
<div id="legend">
<legend class="">Task</legend>
</div>
<div class="control-group">
<!-- Name-->
<label class="control-label">Name</label>
<div class="controls">
<input type="text" name="itemName" ng-model="newItem.name" placeholder="Task Name" class="input-xlarge" ng-required="true" >
<p class="help-block"></p>
</div>
</div>
<div class="control-group">
<!-- Description -->
<label class="control-label">Description</label>
<div class="controls" >
<input type="text" ng-model="newItem.description" placeholder="Task Description" class="input-xlarge">
<p class="help-block"></p>
</div>
</div>
<div class="control-group">
<!-- Button -->
<div class="controls">
<a class="btn icon-btn btn-success" ng-disabled="myForm.$invalid" ng-click="addOrSaveItem()">
<span class="glyphicon btn-glyphicon glyphicon-save img-circle text-success"></span>Save</a>
</div>
</div>
</fieldset>
</form>
</div>
FiddleJS
Look nd Feel

Using "onChange" as an event name is a poor choice as it is likely to conflict with other events with that name. My recommendation is to use the directive's name as part of the event name.
In your directive
angular.module('myApp.items.directive', [])
.directive('itemSwitch', function() {
return {
restrict: 'A',
scope: {
item: '=',
itemIndex: "="
},
template: '<button ng-click="doIt()">Do It</button>',
link : function(scope){
scope.doIt = function() {
scope.$emit('itemSwitch.doIt', scope.item, scope.itemIndex);
};
}
}
});
In your controller
$scope.doItItems = [];
$scope.$on("itemSwitch.doIt", function(item, itemIndex) {
doItItems.push(item);
});
In this example, on each click of the Do It button, an item is pushed to the doItItems list.

In your itemSwitch directive, you can do
$rootScope.$broadcast('onChange', item);
And then you can pick it up in any scope that is listening (in this case, your controller), with
$scope.$on('onChange', function(event, args) { ... }
This works because $broadcast moves downward from parent to children, while $emit moves upward from child to parents.
So for example, $rootScope.$emit would only be picked up by $rootScope.$on since $rootScope has no parents, while $scope.$broadcast would only be available to that scope's children and not to $rootScope.$on, since $rootScope is not a child of $scope

Related

Angularjs passing id into modal function()

I am using angularjs and spring mvc for my application. At first I am displaying all the placements with one button Placed Candidates. I am doing ng-repeat for displaying all the placements.
Here is my html.
<div class="row" data-ng-repeat="placement in placements">
<div class="col-xs-12 col-sm-12 col-md-12">
<h5>{{placement.companyName}}</h5>
</div>
<div class="col-xs-12 col-sm-6 col-md-3">
<h6>{{placement.placementDate | date:'dd MMM yyyy'}}</h6>
Company Target (appox):10 Achieved:
</div>
<a href="javascript:void(0);" data-ng-
click="placedcandidatespopup(placement.placementId);">//here i can
get particular placementId but how to pass this id to
savePlacementStudents() method in controller.js???//
PlacedCandidates
</a>
</div>
I am calling one modal popup function. Here is controller.js
$scope.placedcandidatespopup = function()
{
AdminUsersService.getAllUsers().then(function(response)
{
$scope.allusers=response.data;
console.log($scope.allusers);
})
$(".placedcandidates").modal("show");
}
I am calling modal by name "placedcandidates".
Here is the modal
<div class="modal right fade placedcandidates" id="myModal1"
tabindex="-1" role="dialog" aria-labelledby="myModalLabel2">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="row">
<div class="col-xs-12 col-sm-6 col-md-6" data-ng-
repeat="student in allusers">
<input id="{{student.studentId}}" type="checkbox" value="
{{student.studentId}}" data-ng-
checked="selection.indexOf(student.studentId) >-1"
data-ng-click="toggleSelection(student.studentId)"/>
<label for="{{student.studentId}}"></label>
{{student.firstName}}
</div>
</div>
</div>
<div class="modal-footer" style="position: fixed;">
<input type="button" value="Submit" class="btn" data-ng-
click="savePlacementStudents()">
</div>
</div>
</div>
</div>
After clicking on button popup modal will display with all Students with checkboxes placed beside them.Now I am able to save studentId into database.But I am not able to pass placementId for saving.
Here is my savePlacementStudents method in controller.js.
$scope.selectedStudents = {};
$scope.savePlacementStudents = function(selectedStudents)
{
selectedStudents.selectedList = $scope.selection;
AdminPlacementService.savePlacementStudents(selectedStudents).then
(function(response)
{
if(response.data=="success"){
$window.scrollTo(0,0);
$scope.selectedStudents = {};
$scope.getplacementdetails($scope.currentPageIndex);
$scope.successmessage="Student Selection Saved!;
$timeout(function() {
$scope.successmessage="";
$scope.closeplacedcandidatespopup();
}, 1500);
}
});
}
Can anyone tell how can I pass respective placementId to this saveStudents method() so that I can set that placementId for those students selected.
Here is the solution.
The placementId passed to function placedcandidatespopup can be saved as follows in function placedcandidatespopup
$scope.selectedPlacementId = placementId;
Now the same $scope will have access in Popup also as it is using same controller.js. And now you can use this $scope.selectedPlacementId anywhere in controller.js
So you don't need to pass placementId...!!
Hope that would help you..!!
$scope.placedcandidatespopup = function(id)
{
AdminUsersService.getAllUsers().then(function(response)
{
$scope.allusers=response.data;
console.log($scope.allusers);
})
$scope.inserted = {
placementId:id,
};
var modalInstance = $uibModal.open({
templateUrl: 'views/test/myModal1.html',
controller: 'TestCntrl',
size: "Modelwidth",
resolve: {
items: function () {
return $scope.inserted;
}
}
});
}
In model you have to access using items.placementId and when you savePlacementStudents() method send placementId as well with student info.

Angularjs cannot perform crud operations from accordion

I am trying to add a form to a bootstrap accordion and post a text value. Without accordion the form works alright. When i added the form to the accordion i am not able to pass text box value to angularjs controller. Basically i am not able to perform the CRUD operations as i am not able to pass the values. Minimum code related to the issue.
main.html
<div ng-controller="myController">
<accordion class="accordion" close-others="oneAtATime">
<accordion-group ng-repeat="group in groups" is-open="status.isOpen[$index]" >
<accordion-heading>
{{group.groupTitle}} <i class="fa chevron-icon" ng-class="{'fa-chevron-down': status.isOpen[$index], 'fa-chevron-right': !status.isOpen[$index]}"></i>
</accordion-heading>
<div ng-include="group.templateUrl"></div>
</accordion-group>
</accordion>
</div>
controller.js
App.controller('myController', ['$scope', 'Parts', function($scope, Parts) {
var original = $scope.parts;
$scope.submit = function() {
if ($scope.parts.a_id == null) {
$scope.createNewPart();
} else {
$scope.updatePart();
}
};
$scope.createNewPart = function() {
Parts.resource1.create($scope.parts);
};
$scope.updatePart = function() {
Parts.resource2.update($scope.parts)
};
$scope.oneAtATime = true;
$scope.groups = [{
groupTitle: "ADD 1",
templateUrl: "file1.html"
}, {
groupTitle: "ADD 2",
templateUrl: "file2.html"
}];
$scope.status = {
isOpen: new Array($scope.groups.length)
};
for (var i = 0; i < $scope.status.isOpen.length; i++) {
$scope.status.isOpen[i] = (i === 0);
}
} ]);
file1.html
<div>
<form name="myForm" ng-submit="submit()">
<div class="row">
<label class="col-md-2 control-lable" for="part">Add1</label>
<input type="text" ng-model="parts.part" id="part" required />
</div>
<div class="row">
<input type="submit" value="{{!parts.id ? 'Add' : 'Update'}}" ng-disabled="myForm.$invalid">
</div>
</form>
//inside table
<tr ng-repeat="a in availableparts >
<td>{{a.id}}</td>
<td>{{a.apart}}</td>
<td><button type="button" ng-click="editPart(a.id)" >Edit</button>
<button type="button" ng-click="deletePart(a.id)">Remove</button>
</td>
</tr>
</div>
Initially, you need to set $scope.parts to empty object {}, so when you access $scope.parts.id you will have null, instead of error saying id of undefined

Angular Two-Way data-binding not refreshing ng-repeat on $scope change

My functionality is very simple, and I have to imagine very common. I have a page that lists a collection of 'age groups', and provides functionality to add an 'age group'. I simply would like to have the page immediately reflect the change when a new 'age group' is added.
In early versions, I had simply used the $rootScope in my service to put all 'age groups' in the $rootScope when the $http request was completed. On this latest refactoring I am removing all use of $rootScope in my app, and it is becoming problematic.
The code for the HTML view is as follows: [NOTE: all code considered extraneous has been removed from all code snippets]
<div id="tabContent" class="tab-pane fade in active" ng-show="subIsSelected(1)">
<div class="row">
<div class="col-xs-12">
<form class="form-inline">
<button type="button" class="btn btn-success" ng-click="openAddAgeGroup()">Add Age Group</button>
</form>
</div>
</div>
<hr />
<div class="row row-content">
<div class="col-xs-12">
<h4 ng-show="!areAgeGroups()">No current age groups.</h4>
<ul class="media-list" ng-show="areAgeGroups()">
<li class="media" ng-repeat="ageGroup in ageGroups" style="padding:10px; background: lightGray;">
<div class="media-left media-top" >
<img class="media-object img-thumbnail" style="width: 75px;" ng-src="./images/trfc.png" alt="club logo">
</div>
<div class="media-body">
<h3 class="media-heading" style="padding-top: 20px;">{{ageGroup.name}}</h3>
<button type="button" class="btn btn-xs btn-primary" style="width: 50px;" ng-click="openEditAgeGroup(ageGroup)">Edit</button>
<button type="button" class="btn btn-xs btn-danger" style="width: 50px;" ng-click="deleteAgeGroup(ageGroup)">Delete</button>
</div>
</li>
</ul>
</div>
</div>
</div>
when first loaded, the page correctly shows all 'age groups' that are in the $scope.ageGroups array.
On clicking the button to add an 'age group', an ng-dialog is created as follows:
$scope.openAddAgeGroup = function() {
console.log("\n\nOpening dialog to add age group");
ngDialog.open({ template: 'views/addAgeGroup.html', scope: $scope, className: 'ngdialog-theme-default', controller:"HomeController" });
};
and that dialog is populated as such:
<div class="ngdialog-message">
<div class="">
<h3>Add a New Age Group</h3>
</div>
<div> </div>
<div>
<form ng-submit="addAgeGroup()">
<div class="form-group">
<label class="sr-only" for="name">Age Group Display Name</label>
<input type="text" class="form-control" id="name" placeholder="age group name" ng-model="ageGroupForm.name">
</div>
<div class="form-group">
<label class="sr-only" for="birthyear">Birth Year</label>
<input type="text" class="form-control" id="birthyear" placeholder="birth year" ng-model="ageGroupForm.birthyear">
</div>
<div class="form-group">
<label class="sr-only" for="socceryear">Soccer Year</label>
<div class="input-group">
<div class="input-group-addon">U</div>
<input type="text" class="form-control" id="socceryear" placeholder="soccer year" ng-model="ageGroupForm.socceryear">
</div>
</div>
<button type="submit" class="btn btn-info">Add</button>
<button type="button" class="btn btn-default" ng-click=closeThisDialog("Cancel")>Cancel</button>
</form>
</div>
</div>
When the form is submitted, the 'age group' is added to the database, from the controller:
'use strict';
angular.module('ma-app')
.controller('HomeController', ['$scope', 'ngDialog', '$state', 'authService', 'coreDataService', 'userService', '$rootScope', 'clubService', 'schedulingService', function($scope, ngDialog, $state, authService, coreDataService, userService, $rootScope, clubService, schedulingService) {
...
$scope.addAgeGroup = function() {
coreDataService.addAgeGroup($scope.ageGroupForm)
.then(function(response) {
coreDataService.refreshAgeGroups()
.then(function(response) {
coreDataService.setAgeGroups(response.data);
$scope.ageGroups = coreDataService.getAgeGroups();
console.log("\n\n\nretrieved age groups and put them in scope");
console.log($scope.ageGroups);
ngDialog.close();
});
}, function(errResponse) {
console.log("Failed on attempt to add age group:");
console.log(errResponse);
});
};
The coreDataService is defined as follows:
'use strict';
angular.module('ma-app')
.service('coreDataService', ['$http', 'baseURL', 'googleGeolocateBaseURL', 'googleGeocodeKey', 'googleMapsBaseURL', function($http, baseURL, googleGeolocateBaseURL, googleGeocodeKey, googleMapsBaseURL) {
var ageGroups = {};
var ageGroupsLoaded = false;
this.getAgeGroups = function() {
return ageGroups;
};
this.setAgeGroups = function(newAgeGroups) {
ageGroups = newAgeGroups;
ageGroupsLoaded = true;
};
this.addAgeGroup = function(formData) {
//post age group:
var postString = '{ "birth_year": "' + formData.birthyear + '", "soccer_year": "U' + formData.socceryear + '", "name": "' + formData.name + '" }';
console.log("Posting age group with string: " + postString);
return $http({
url: baseURL + 'age_groups/',
method: 'POST',
headers: {
'content-type': 'application/json'
},
data: postString
});
};
}]);
So, when an 'age group' is added, console logging indicates that the new 'age group' is in the collection now stored in the $scope.ageGroups array, but the HTML's ng-repeat does not reflect the new 'age group'. Only when I navigate to another tab in the interface, and then return to the tab containing the 'age groups' is the newly added 'age group' displayed.
Updated:
After you update your value you need to tell angular that its been updated. If you wrap your code in a $timeout. On the next digest cycle the view will get updated. See here for more information.
...
$timeout(function() {
$scope.ageGroups = coreDataService.getAgeGroups();
// anything you want can go here and will safely be run on the next digest.
})
...
The $timeout basically runs on the next $digest cycle thus updating your value in the view.
I would suggest wrapping your ng-repeat part in a directive, fire an event on add method and listen to it from the directive:
this.addAgeGroup = function(formData) {
// do stuff
$scope.$broadcast('itemadded');
}
Inside your directive link function:
$scope.$on('itemadded', function() {
$scope.ageGroups = coreDataService.getAgeGroups();
});
I would recommend using the ControllerAs syntax which will refresh the ng-repeat, if the array to which it is bound to gets updated.
Refer the answer given here - ng-repeat not updating on update of array

ng-dialog: input radio button value is undefined

I'm trying to get the radio value button value from ng-dialog but it always got undefined.
Here is the dialog template:
<script type="text/ng-template" id="flag-reasons.html">
<div style="padding:10px;">
<span>
What's wrong ?
</span>
<div class="form-group" ng-repeat="flagreason in flagreasons">
<input type="radio" ng-model="fr" name="frname" value="{{flagreason.id}}"> {{flagreason.title}}
</div>
<div class="form-group">
<button type="button" ng-click="validFlag()">
Valider
</button>
</div>
</div>
</script>
Here is the partial js where I start with the dialog:
$scope.openFlag = function(){
$scope.dialog = ngDialog.open({ template: 'flag-reasons.html',
className: 'ngdialog-theme-default', scope: $scope });
$scope.validFlag = function(){
console.log($scope.fr);
}
}
I have tried ng-value as below:
<input type="radio" ng-model="fr" name="frname" ng-value="flagreason.id"> {{flagreason.title}}
but it still got undefined
Notice that it works when I directly set the value of the radio button like:
<input type="radio" ng-model="fr" name="frname" value="5"> {{flagreason.title}}
The issue is with ngmodel that's not getting update. You have to initialise ngmodel first in the template.
flag-reasons.html
<script type="text/ng-template" id="flag-reasons.html">
<div style="padding:10px;">
<span>
What's wrong ?
</span>
<div class="form-group" ng-repeat="flagreason in flagreasons">
<input type="radio" ng-model="cb.fr" name="frname" value="{{flagreason.id}}">{{flagreason.id}} - {{flagreason.title}}
</div>
<div class="form-group">
<button type="button" ng-click="validFlag()">
Valider
</button>
</div>
</div>
</script>
Controller
angular.module("app", ['ngDialog'])
.controller('Ctrl',function ($scope, ngDialog) {
'use strict';
$scope.cb = {};
$scope.flagreasons = [
{id: 1, title: 'title1'},
{id: 2, title: 'title2'},
{id: 3, title: 'title3'}
];
$scope.openFlag = function(){
$scope.dialog = ngDialog.open({ template: 'flag-reasons.html',
className: 'ngdialog-theme-default', scope: $scope });
$scope.validFlag = function(){
console.log($scope.fr);
}
}
$scope.$watch('cb.fr', function (v) {
console.log(v);
});
});
Working Fiddle: JSFiddle

Method display on the scope is not executed

I have a set of directives that share a scope
Update:
The code is available as a plunk here http://plnkr.co/edit/JZ77bsZgGrw6N4K718Is?p=preview
todo-item:
app.directive("todoItem",function(DeleteTodo,$log){
var dirDefObj = {
restrict:'E',
templateUrl:'app/templates/todo.html',
scope:{
todo:'=value'
},
controller:function($scope){},
replace:true
};
return dirDefObj;
});
template:
<div class="ui card">
<todo-formui ng-show="todo.editMode"></todo-formui>
<todo-cardui ng-show="!todo.editMode"></todo-cardui>
</div>
There are two directives that inherit the scope of this directive:
todo-cardui
app.directive('todoCardui',function(){
var dirDefObj = {
restrict:'E',
templateUrl:'app/templates/display-todo.html',
scope:false,
replace:true,
controller:function($scope)
{
$scope.clickDone = function clickDone(){
//two tasks (1)toggle the done value on the todo (2) toggle the btnText on the todo
$scope.todo.done = !$scope.todo.done;
$scope.todo.btnText = $scope.todo.done?'Reinstate':'Done';
};
$scope.remove = function remove()
{
DeleteTodo.delete($scope.todo);
$scope.$emit('todo:deleted',$scope.todo);
};
$scope.edit = function edit(value)
{
$scope.todo.editMode = true;
$scope.savedState = angular.extend({},$scope.todo);
};
}
};
return dirDefObj;
});
template:
<div>
<div class="content">
<i class="right floated blue pencil icon" ng-click="edit()"></i>
<header class="ui medium header">
<span ng-class="{'done-todo':todo.done,'normal-todo':!todo.done}">{{todo.task}}</span>
</header>
<div class="description">
<p>{{todo.description}}</p>
</div>
</div>
<div class="extra content">
<button class="ui small green toggle button floated left" ng-click="clickDone()">{{todo.btnText}}</button>
<button class="ui small red button floated left" ng-click="remove()">Delete</button>
</div>
</div>
todo-formui:
app.directive("todoFormui",function(EditService){
var dirDefObj = {
restrict:'E',
templateUrl:'app/templates/edit-todo.html',
scope:false,
controller:function($scope){
$scope.display = function display(){
console.log("Inside the edit to preview function");
$scope.editMode = false;
};
$scope.save = function(){
EditService.edit($scope.todo);
};
$scope.discard = function(){
$scope.todo={
task:'',
dscription:'',
btnText:''
};
$scope.todo = $scope.savedState;
};
},
replace:true
};
return dirDefObj;
});
template:
<div ng-class="{description:show_modal,content:!show_modal}">
<i class="right floated blue unhide icon" ng-click="display()"></i>
<form class="ui small form">
<div class="field">
<label>Task</label>
<input type="text" name="task" placeholder="What do you want to do?"ng-model="todo.task">
</div>
<div class="field">
<label>Description</label>
<textarea ng-model="todo.description"></textarea>
</div>
<div class="two fields">
<button class="ui red button floated right">Discard</button>
<button class="ui submit green button floated right" ng-click="save()">Save</button>
</div>
</form>
When executing the code,I found that the display function in todo-formui would not execute,no matter where I put it or what I tried to do.The save function in the same scope runs with no problems.
In your preview function(as mentioned in plunker), you have to update the scope as
$scope.todo.editMode = false;
instead of
$scope.editMode = false;
then the preview will be avaliable
Hope this helps
It's because the icon is under another html element and the click event isn't triggered. Add a clear after the "preview" icon (which is floated) to push the form where it should be. Use some DOM inspector and you will soon realize the problem.

Resources