angularjs scope function of a repeated directive - angularjs

I am trying to have a directive with a repeat on it and have it call a function on the parent control as well as child controls. however when I add a scope: { function:&function}
the repeat stops working properly.
fiddle
the main.html is something like
<div ng-app="my-app" ng-controller="MainController">
<div>
<ul>
<name-row ng-repeat="media in mediaArray" on-delete="delete(index)" >
</name-row>
</ul>
</div>
</div>
main.js
var module = angular.module('my-app', []);
function MainController($scope)
{
$scope.mediaArray = [
{title: "predator"},
{title: "alien"}
];
$scope.setSelected = function (index){
alert("called from outside directive");
};
$scope.delete = function (index) {
alert("calling delete with index " + index);
}
}
module.directive('nameRow', function() {
return {
restrict: 'E',
replace: true,
priority: 1001, // since ng-repeat has priority of 1000
controller: function($scope) {
$scope.setSelected = function (index){
alert("called from inside directive");
}
},
/*uncommenting this breaks the ng-repeat*/
/*
scope: {
'delete': '&onDelete'
},
*/
template:
' <li>' +
' <button ng-click="delete($index);">' +
' {{$index}} - {{media.title}}' +
' </button>' +
' </li>'
};
});

As klauskpm said is better to move common logic to an independent service or factory. But the problem that i see is that the ng-repeat is in the same element of your directive. Try embed your directive in an element inside the loop and pass the function in the attribute of that element or create a template in your directive that use the ng-repeat in the template
<li ng-repeat="media in mediaArray" >
<name-row on-delete="delete(media)" ></name-row>
</li>

As I've suggested you, the better approach to share methods is building a Factory or a Service, just like bellow:
app.factory('YourFactory', function(){
return {
setSelected: function (index){
alert("called from inside directive");
}
}
};
And you would call it like this:
function MainController($scope, YourFactory) {
$scope.setSelected = YourFactory.setSelected;
// Could even use $scope.yf = YourFactory;, and call yf.setSelected(index);
// at your view.
(...)
module.directive('nameRow', function(YourFactory) {
(...)
$scope.setSelected = YourFactory.setSelected;
(...)
Hope it will help you.

Related

ng-click does not work in directive of inline magnific popup

I have created magnific popup directive in angularjs. It works fine ng-click event on description link when first time loads but after close popup and again click on description link it does not work.
Please refer below code:
PhotoCtrl.js
(function () {
'use strict';
function PhotoCtrl($rootScope, $scope, $state, HzServices, HzPhotoService) {
$scope.doAlbumList = function () {
var data = {q: $state.params.pid};
$scope.albums = {};
$scope.albumsCount = 0;
$scope.photos = {};
$scope.photosCount = 0;
$rootScope.arrData = [];
var deferred = HzServices.deferred("/api/album/list", 'GET', data);
deferred.then(
function (res) {
/*
* success in repsonse
* Share common photo & album data across all controllers, directives by services.
*/
var data = {album: {count: res.data.count.album, data: res.data.album}, photo: {count: res.data.count.photo, data: res.data.photo}};
/*
* Create an array of magnific inline popup content
*/
angular.forEach(data.photo.data, function (value, key) {
if (value.data.length > 0) {
angular.forEach(value.data, function (value_, key_) {
$rootScope.arrData.push({
imageDescription: value_.photo_description,
imageScale_img: "/resize/" + value_.module + "/" + value_.photo_name,
imageOriginal_href: "/" + value_.module + "/" + value_.photo_name
});
});
}
});
HzPhotoService.setSharedData(data);
$scope.albums = $rootScope.sharedData.album.data;
$scope.albumsCount = $rootScope.sharedData.album.count;
$scope.photos = $rootScope.sharedData.photo.data;
$scope.photosCount = $rootScope.sharedData.photo.count;
},
function (res) {
/*
* Error hading in repsonse
*/
var data = {album: {count: $scope.albumsCount, data: $scope.albums}, photo: {count: $scope.photosCount, data: $scope.photos}};
HzPhotoService.setSharedData(data);
}
);
}
/**
* Get Photos data & count
* #returns {Array}
*/
$scope.doGetPhoto = function () {
return [{photos: $scope.photos, photoCount: $scope.photoCount}];
}
$scope.doEditDescription = function () {
console.log("description links from controller called");
}
angular
.module("AppWhizbite")
.controller('PhotoCtrl', ['$rootScope', '$scope', '$state', 'HzServices', 'HzPhotoService', PhotoCtrl]);
}());
photoList.html
<div>
<div class="total_album_photo gallery" ng-repeat-start="photo in photos track by $index">
<div class="no_of_photo imgWrapper">
<a href="javascript:void(0);" class="popup-link" data-index="{{$index}}">
<img ng-src="/resize/photo/{{photo.photo_name}}" height="120" width="120"/>
</a>
</div>
</div>
<div ng-repeat-end=""><hz-photo-popup></hz-photo-popup></div>
</div>
hzPhotoDirective.js
(function () {
'use strict';
angular
.module("AppWhizbite")
.directive("hzPhotoPopup", ["$rootScope", "$compile", "HzPhotoService", function ($rootScope, $compile, HzPhotoService) {
var magnificMarkup = "\n\
<form ng-controller=\"PhotoCtrl as Photo\" class=\"white-popup-block popMarkup ng-pristine ng-valid ng-scope\" id=\"dataPopup\" >\n\
<div class=\"popup_heading\">Photo</div>\n\
<div id=\"img_center\">\n\
<img style=\"width:100%\" src=\"\" id=\"img_center_content\" class=\"mfp-imageScale\">\n\
</div>\n\
<div class=\"popup_main\">\n\
<div class=\"popup_left photo_popup_left\">\n\
<div class=\"popup_raw1\">\n\
Edit description\n\
<div class=\"mfp-imageDescription\" style=\"cursor:pointer;\" ng-click=\"doEditDescription()\"></div>\n\
<textarea class=\"submitByEnter commentarea mfp-imageDescription\" placeholder=\"Edit description\" style=\"height: 76px;display:none;\"></textarea>\n\
</div>\n\
</div>\n\
</div>\n\
<div class=\"video_main\">\n\
</div>\n\
<button class=\"mfp-close\" type=\"button\" title=\"Close (Esc)\">×</button>\n\
</form>";
return {
restrict: "AE",
replace: false,
scope: true,
compile: function (scope, element) {
return{
pre: function (scope, element, attrs) {
if (scope.$last) {
// Iterate through all thumbnails class to bind magnific popup plugins
angular.forEach(angular.element(".gallery > .imgWrapper > a"), function (val, key) {
angular.element(".popup-link").eq(key).magnificPopup({
key: 'my-popup',
//items: arrData, // Array of media details
items: $rootScope.arrData, // Array of media details
index: key, // Index of media ref: data-index
type: 'inline',
verticalFit: true, // Fits image in area vertically
inline: {
// Magnific popup custom markup to show media (photo) gallery
markup: $compile(magnificMarkup)(scope)
},
gallery: {
enabled: true
},
callbacks: {
open: function () {
console.log("open called");
},
change: function () {
console.log("cahnge callaed");
},
markupParse: function (template, values, item) {
// optionally apply your own logic - modify "template" element based on data in "values"
// console.log('Parsing:', template, values, item);
console.log("markup parse called");
},
elementParse: function (item) {
console.log("element parse called");
}
}
});
});
}
}
}
},
link: function (scope, element, attrs) {
console.log("link method called");
}
}
}]);
}());
After R&D I crack the issue.
Magnific Popup callbacks objects have markupParse() method. It calls in every action of popup, so I put my angular js template $compile in markupParse method and it works fine.
It may different as per conditions or situations, but almost in all conditions it works finally fine.
Code:
inline: {
// Magnific popup custom markup to show media (photo) gallery
markup: magnificMarkup
},
callbacks:{
markupParse: function (template, values, item) {
$compile(template)(scope);
}
}
In the markupParse method having 3 parameters:
template : template holds actual HTML template which use in popup.
values: value holds current indexed value from arrData
item: item hold current item object.

Drawing morris chart in angular directive almost shows up

I'm trying to draw a morris chart in an angular directive that is within an ng-repeat block. It is weird, because it draws, almost? I can see it's there and the mouseovers work, but the graph itself is only a thin line at the top. Does anybody have any ideas?
Here's the html:
<div id="page-wrapper" ng-repeat="d in dealerGroup.Dealerships__r" ng-if="expandedDealer == d.Id">
<div class="panel-heading">Area Chart Example</div>
<div class="panel-body">
<area-chart dealership="d" chartData="d.SalesChartData"></area-chart>
</div>
</div>
And here's the directive
angular.module('areaChart', ['ui.bootstrap']).directive('areaChart', function($window) {
var directive = {};
// directive.templateUrl = directivePath + '/charts/area-chart.html';
directive.restrict = 'EA';
directive.scope = {
dealership: "=",
chartdata: "="
};
directive.controller = function($scope) {
$scope.ykeys = function() {
var ykeys = [];
angular.forEach($scope.chartdata, function(d,k) {
angular.forEach(d, function(value,key) {
if(key != 'period') { ykeys.push(key); }
})
});
return ykeys;
}
}
directive.link = function($scope,element,attrs) {
Morris.Area({
element: element,
xkey: 'period',
ykeys: $scope.ykeys(),
labels: $scope.ykeys(),
hideHover: 'auto',
pointSize: 2,
data: $scope.chartdata
});
}
return directive;
});
And here's what happens:
Additionally, resizing makes the whole thing blow up with javascript errors everywhere. But i'll worry that separately

angularjs - Sharing data between controllers through service

I have a 2 controllers [FirstController,SecondController] sharing two arrays of data (myFileList,dummyList) through a service called filecomm.
There is one attribute directive filesread with isolated scope that is bound to a file input in order to get the array of files from it.
My problem is that myFileList array in my service never gets updated when I select the files with the input. However, dummyList array gets updated immediately in the second div (inner2). Does anybody know why is this happening?
For some reason in the second ngrepeat when I switch from (fi in secondCtrl.dummyList) to (fi in secondCtrl.myFileList) it stops working.
Any help would be greatly appreciated.
Markup
<div ng-app="myApp" id="outer">
<div id="inner1" ng-controller="FirstController as firstCtrl">
<input type="file" id="txtFile" name="txtFile"
maxlength="5" multiple accept=".csv"
filesread="firstCtrl.myFileList"
update-data="firstCtrl.updateData(firstCtrl.myFileList)"/>
<div>
<ul>
<li ng-repeat="item in firstCtrl.myFileList">
<fileuploadrow my-file="item"></fileuploadrow>
</li>
</ul>
</div>
<button id="btnUpload" ng-click="firstCtrl.uploadFiles()"
ng-disabled="firstCtrl.disableUpload()">Upload
</button>
</div>
<div id="inner2" ng-controller="SecondController as secondCtrl">
<ul ng-repeat="fi in secondCtrl.dummyList">
<li>Hello</li>
</ul>
</div>
</div>
JS
angular.module('myApp', [])
.controller('FirstController',
['$scope','filecomm',function ($scope,filecomm) {
this.myFileList = filecomm.myFileList;
this.disableUpload = function () {
if (this.myFileList) {
return (this.myFileList.length === 0);
}
return false;
};
this.uploadFiles = function () {
var numFiles = this.myFileList.length;
var numDummies = this.dummyList.length;
filecomm.addDummy('dummy no' + numDummies + 1);
console.log('Files uploaded when clicked:' + numFiles);
console.log('dummy is now:'+ this.dummyList.length);
};
this.updateData = function(newData){
filecomm.updateData(newData);
console.log('updated data first controller:' + newData.length);
};
this.dummyList = filecomm.dummyList;
console.log('length at init:' + this.myFileList.length);
}]) //FirstController
.controller('SecondController',
['$scope', 'filecomm', function($scope,filecomm) {
var self = this;
self.myFileList = filecomm.myFileList;
self.dummyList = filecomm.dummyList;
console.log('SecondController myFileList - length at init:' +
self.myFileList.length);
console.log('ProgressDialogController dummyList - length at init:' +
self.dummyList.length);
}]) //Second Controller
.directive('filesread',[function () {
return {
restrict: 'A',
scope: {
filesread: '=',
updateData: '&'
},
link: function (scope, elm, attrs) {
scope.$watch('filesread',function(newVal, oldVal){
console.log('filesread changed to length:' +
scope.filesread.length);
});
scope.dataFileChangedFunc = function(){
scope.updateData();
console.log('calling data update from directive:' +
scope.filesread.length);
};
elm.bind('change', function (evt) {
scope.$apply(function () {
scope.filesread = evt.target.files;
console.log(scope.filesread.length);
console.log(scope.filesread);
});
scope.dataFileChangedFunc();
});
}
}
}]) //filesread directive
.directive('fileuploadrow', function () {
return {
restrict: 'E',
scope: {
myFile: '='
},
template: '{{myFile.name}} - {{myFile.size}} bytes'
};
}) //fileuploadrow directive
.service('filecomm', function FileComm() {
var self = this;;
self.myFileList = [];
self.dummyList = ["item1", "item2"];
self.updateData = function(newData){
self.myFileList= newData;
console.log('Service updating data:' + self.myFileList.length);
};
self.addDummy = function(newDummy){
self.dummyList.push(newDummy);
};
}); //filecomm service
Please see the following
JSFiddle
How to test:
select 1 or more .csv file(s) and see each file being listed underneath.
For each file selected the ngrepeat in the second div should display Hello. That is not the case.
Change the ngrepat in the second div to secondCtrl.dummyList
Once you select a file and start clicking upload, you will see that for every click a new list item is added to the ul.
Why does dummyList gets updated and myFileList does not?
You had a couple of issues.
First, in the filecomm service updateData function you were replacing the list instead of updating it.
Second, the change wasn't updating the view immediately, I solved this by adding $rootScope.$apply which forced the view to update.
Updated JSFiddle, let me know if this isn't what you were looking for https://jsfiddle.net/bdeczqc3/76/
.service('filecomm', ["$rootScope" ,function FileComm($rootScope) {
var self = this;
self.myFileList = [];
self.updateData = function(newData){
$rootScope.$apply(function(){
self.myFileList.length = 0;
self.myFileList.push.apply(self.myFileList, newData);
console.log('Service updating data:' + self.myFileList.length);
});
};
}]); //filecomm service
Alternately you could do the $scope.$apply in the updateData function in your FirstController instead of doing $rootScope.$apply in the filecomm service.
Alternate JSFiddle: https://jsfiddle.net/bdeczqc3/77/
this.updateData = function(newData){
$scope.$apply(function(){
filecomm.updateData(newData);
console.log('updated data first controller:' + newData.length);
});
};

Word counter in angularjs

I'm a newbie in angular so please bear with me. I have a character counter and word counter in my textarea. My problem is that everytime I press the space, it is also being counted by getWordCounter function. How can I fix this? Thank you in advance.
HTML:
<textarea id="notesContent" type="text" class="form-control" rows="10" ng-model="notesNode.text" ng-trim="false" maxlength="5000"></textarea>
<span class="wordCount">{{getWordCounter()}}</span>
<span style="float:right">{{getCharCounter()}} / 5000</span>
JS:
$scope.getCharCounter = function() {
return 5000 - notesNode.text.length;
}
$scope.getWordCounter = function() {
return $.trim(notesNode.text.split(' ').length);
}
It seems like you need to call 'trim' before calling split, like this:
$scope.getWordCounter = function() {
return notesNode.text.trim().split(' ').length;
}
If you want to support multiple spaces between words, use a regular expression instead:
$scope.getWordCounter = function() {
return notesNode.text.trim().split(/\s+/).length;
}
Filter implementation
You can also implement wordCounter as a filter, to make it reusable among different views:
myApp.filter('wordCounter', function () {
return function (value) {
if (value && (typeof value === 'string')) {
return value.trim().split(/\s+/).length;
} else {
return 0;
}
};
});
Then, in the view, use it like this:
<span class="wordCount">{{notesNode.text|wordCounter}</span>
See Example on JSFiddle
This is a more advanced answer for your problem, since it can be reusable as a directive:
var App = angular.module('app', []);
App.controller('Main', ['$scope', function($scope){
var notesNode = {
text: '',
counter: 0
};
this.notesNode = notesNode;
}]);
App.directive('counter', [function(){
return {
restrict: 'A',
scope: {
counter: '='
},
require: '?ngModel',
link: function(scope, el, attr, model) {
if (!model) { return; }
model.$viewChangeListeners.push(function(){
var count = model.$viewValue.split(/\b/g).filter(function(i){
return !/^\s+$/.test(i);
}).length;
scope.counter = count;
});
}
};
}]);
And the HTML
<body ng-app="app">
<div ng-controller="Main as main"></div>
<input type="text" ng-model="main.notesNode.text" class="county" counter="main.notesNode.counter">
<span ng-bind="main.notesNode.counter"></span>
</body>
See it in here http://plnkr.co/edit/9blLIiaMg0V3nbOG7SKo?p=preview
It creates a two way data binding to where the count should go, and update it automatically for you. No need for extra shovelling inside your scope and controllers code, plus you can reuse it in any other input.

Angularjs. Accessing attributes from an AngularJS controller

I'm trying to access image src with controller to save it, but can not figure out how to do it.
My template:
<img data-ng-model="book.image"
style="width: 300px; height: 200px;"
ng-src="data:image/png;base64,iVBORw0K...SuQmCC">
<a data-ng-click="save(book)" class="btn">Submit</a>
My controller:
controller('BookEditController', [ '$scope', '$meteor', function ($scope, $meteor) {
$scope.save = function (book) {
if (typeof book == 'object') {
var books = $meteor("books");
var id = books.insert(book);
}
};
}])
One option is using a directive and applying a method called save to it which would handle the src attribute found on the image tag.
JS
var app = angular.module('myApp', []);
app.directive('saveImage', function () {
return {
transclude: true,
link: function (s, e, a, c) {
s.save=function(){
alert(a.src);
};
}
};
});
HTML
<div >
<img save-image style="width: 300px; height: 200px;" src="http://placehold.it/350x150"> <a ng-click="save()" class="btn">Submit</a>
</div>
This is the code implemented in jsfiddle.
Another option is to isolate the scope to a controller but still apply the image to it instead of a function.
JS
var app = angular.module('myApp', []);
app.directive('saveImage', function () {
return {
transclude: true,
link: function (s, e, a, c) {
s.image = a.src;
}
};
});
function cntl($scope) {
$scope.save = function (img) {
alert($scope.image || 'no image');
}
}
HTML
<div ng-controller='cntl'>
<img save-image style="width: 300px; height: 200px;" src="http://placehold.it/350x150"> <a ng-click="save()" class="btn">Submit</a>
</div>
Notice the added ng-controller="cntl".
This is the JSfiddle for that one.
There's probably a better way to do this... pass $event to your controller function
<a data-ng-click="save(book, $event)" class="btn">Submit</a>
and then use traversal methods to find the img tag and its src attr:
$scope.save = function (book, ev) {
console.log(angular.element(ev.srcElement).parent().find('img')[0].src);
...
Update: the better way is to create a directive (like #mitch did), but I would use = binding in an isolate scope to update a src property in the parent scope. (The = makes it clear that the directive may alter the scope. I think this is better than having a directive add a method or a property to the controller's scope "behind the scenes".)
<div ng-controller="MyCtrl">
<img save-image book="book1" src="http://placehold.it/350x150" >
Submit
</div>
function MyCtrl($scope) {
$scope.book1 = {title: "book1" }; // src will be added by directive
$scope.save = function(book) {
alert(book.title + ' ' + book.src);
}
}
app.directive('saveImage', function () {
return {
scope: { book: '=' },
link: function (s, e, a, c) {
s.book.src = a.src;
}
};
});
Plunker

Resources