I'm trying to write a directive for fancytree. The source is loaded through ajax and almost everything looks like a charm. The tree is correctly shown, events are firing nice, but the parameters get undefined at the controller side.
It looks strange, because when I set a function(event, data){ ... } for the events (like activate or beforeSelect as seen in the docs) both event and data are nicely set.
Where I'm doing it wrong?
Thank you in advance!
Directive
angular.module('MyAppModule', [])
.provider('MyAppModuleConfig', function () {
this.$get = function () {
return this;
};
})
.directive('fancytree', function () {
return {
restrict: 'E',
transclude: true,
replace: true,
scope: {
activateFn: '&',
//long list of events, all stated with "<sth>Fn : '&'"
selectFn: '&',
selectedNode: '=',
treeviewSource: '=',
enabledExtensions: '=',
filterOptions: '='
},
template: '<div id="treeview-container"></div>',
link: function (scope, element) {
element.fancytree({
source: scope.treeviewSource,
activate: function (event, data) {
console.log(event, data); // ok, parameters are all set
scope.activateFn(event, data);
// function fires right, but all parameters
// are logged as undefined
}
});
}
};
});
HTML
<fancytree ng-if="tvSource" treeview-source="tvSource"
activate-fn="genericEvt(event, data)"/>
Controller
TreeViewSvc.query()
.success(function (response) {
$timeout(function ()
{
$scope.tvSource = response;
});
});
$scope.genericEvt = function (event, data) {
console.log('event', event);
console.log('data', data);
// function is firing, but all parameters come undefined
};
You are missing one important piece in the function binding of directive. They need to be passed in as object with property name same as that of the argument names. i.e
scope.activateFn(event, data);
should be
scope.activateFn({event: event,data: data});
Or in otherwords, the properties of the object passed in through the bound function ({event: e,data: d}) needs to be specified as argument of the function being bound (genericEvt(event, data)) at the consumer side.
Though the syntax can be confusing at the beginning, you can as well use = binding instead of & though & is to be used specifically for function binding. Ex:
....
activateFn: '=',
....
and
activate-fn="genericEvt"
Related
I have a directive for users to like (or "fave") posts in my application. Throughout my controllers I use $rootScope.$emit('name-of-function', some-id) to update user data when they like a new post, as this is reflected throughout my application. But I can't seem to use $rootScope.$emit in the directive. I receive an error
$rootScope.$emit is not a function
Presumably the $rootScope.$on event which corresponds with this command has not been called yet, so this function does not yet exist? What can be done about this? Is there a better way to arrange this?
var module = angular.module('directives.module');
module.directive('postFave', function (contentService, $rootScope) {
return {
restrict: 'E',
templateUrl: 'directives/post-fave.html',
scope: {
contentId: '#',
slug: '#'
},
link: function ($scope, $rootScope, element) {
$scope.contentFavToggle = function (ev) {
contentId = $scope.contentId;
contentService.contentFavToggle(contentId, ev).then(function (response) {
$rootScope.$emit('dataUpdated', $scope.slug);
if (response) {
$scope.favourite[contentId] = response;
} else {
$scope.favourite[contentId] = null;
}
});
};
console.log("track fave directive called");
}
};
});
from controller:
var dataUpdatedListener = $rootScope.$on('dataUpdated', function (event, slug) {
dataService.clearData(slug);
dataControllerInit();
});
How can I access this rootscope function from within the directive? Thanks.
FYI - "link" has been used in the directive because this is related to an instance of an HTML element which will be used a number of times on the page
link has the following signature, there is no need to add $rootScope injection into link function:
function link(scope, element, attrs, controller, transcludeFn) { ... }
Remove it from link and it will work.
I am trying to make an element draggable using jQuery and Angular directives:
function dragElement($parse) {
return {
restrict: 'A',
link: function($scope, ele, $attr) {
var onDragStart = $attr.onDragStart ? $parse($attr.onDragStart) : null;
var dragData = $scope.$eval($attr.dragData) || ele;
ele.draggable({
containment: 'document',
revert: true,
helper: "clone"
});
ele.on("dragstart", handleDragStart);
function handleDragStart(e) {
if (onDragStart) {
$scope.$apply(function() {
var locals = {
$event: e,
$dragData: dragData,
};
onDragStart($scope, locals); //locals are defined fine here
});
}
}
}
};
}
And in my controller, I have a function which is invoked when i start dragging. The function is getting invoked correctly, but the issue is that the arguments list is empty and my locals parsed in the directive are not available.
Controller Function
function handleFeatureDragStart($event, data) {
console.log(arguments); //Empty
console.log('inside handle feature start');
console.log($event); //undefined
console.log(data);//undefined
}
I can't see, where you fire your handleFeatureDragStart function.
If 'onDragStart' is the function you want, you passing the arguments in a wrong way and use them incorrectly.
handleFeatureDragStart(e, data);
function handleFeatureDragStart(e, data) {}
'arguments' is empty, because you don't pass additional parameters.
I'm building an Angular pop-up system for multiple purposes. The way it works is that I have a directive called bitPopup which three variables get passed on to (type, action and data) as shown below:
index.html
<bit-popup type="popup.type" action="popup.action" data="popup.data"></bit-popup>
popup.js
app.directive('bitPopup', function () {
return {
restrict: 'E',
template: html,
scope: {
type: '=',
action: '=',
data: '='
},
[***]
}
}
The popup controller then loads a different directive based on the type:
popup.html (The HTML template shown above)
<div class="pop-up" ng-class="{visible: visible}" ng-switch="type">
<bit-false-positive-popup ng-switch-when="falsePositive" type="type" action="action" data="data"></bit-false-positive-popup>
</div>
false_positives.js (Containing the bitFalsePositivePopup directive)
[...]
scope: {
type: '=',
action: '=',
data: '='
}
[...]
And then the html template for the bitFalsePositivePopup directive displays some properties from data.
Now the way I'm triggering a pop-up works like this:
From a template inside a directive containing the bitPopup directive i'll change $scope.popup's type, action and data.
I'll do $scope.$broadcast('showPopup');
The bitPopup directive will react because of $scope.$on('showPopup', [...]}); and makes the pop-up visible.
Now this really weird thing occurs where it works on the first try (the pop-up opens with the correct data information), but after the first try it will display the data from the previous try.
Now what's even weirder is that I tried logging the information on the first try and what I found out is that:
$scope.popup at index.html just before calling $scope.$broadcast('showPopup'); displays the right information.
$scope.data at the bitPopup directive displays null
$scope.data at the bitFalsePositivePopup directive displays the right information.
On the second try:
$scope.popup at index.html is correct again.
$scope.data at the bitPopup directive displays the information from the previous attempt
The same holds for the bitFalsePositivePopup directive.
Another weird thing is that when I use $scope.$apply() it does work correctly, only it displays the $apply already in progress error. I know I shouldn't use $scope.$apply() in this case, because it's all Angular events. But how is it possible that the passed scope is always a step behind?
Am I doing something wrong to begin with?
EDIT:
Because of amahfouz's answer I decided to post some more code for clarification. I left out some unimportant details for more clear reading.
index.html
<div class="falsePositives" ng-controller="falsePositives">
<i class="fa fa-minus color-red" ng-click="triggerPopup('falsePositive', 'delete', {detection: getDetection(row.detection, row.source), source: row.source, triggers: row.triggers, hash: row.hash, date: row.date})"></i>
<i class="fa fa-pencil" ng-click="triggerPopup('falsePositive', 'edit', {detection: getDetection(row.detection, row.source), source: row.source, triggers: row.triggers, hash: row.hash, date: row.date})"></i>
<bit-popup type="popup.type" action="popup.action" data="popup.data"></bit-popup>
</div>
index.js
var app = require('ui/modules').get('apps/falsePositives');
app.controller('falsePositives', function ($scope, $http, keyTools, bitbrainTools, stringTools) {
function init() {
$scope.getDetection = getDetection;
$scope.popup = {
type: null,
action: null,
data: null
};
}
function getDetection(hash, source) {
return {
'ids': 'BitSensor/HTTP/CSRF',
'name': 'CSRF Detection',
'description': 'Cross domain POST, usually CSRF attack',
'type': [
'csrf'
],
'severity': 1,
'certainty': 1,
'successful': false,
'input': ['s'],
'errors': []
};
}
$scope.triggerPopup = function (type, action, data) {
$scope.popup = {
type: angular.copy(type),
action: angular.copy(action),
data: angular.copy(data)
};
test();
$scope.$broadcast('showPopup');
};
function test() {
console.log('$scope.popup: ', $scope.popup);
}
}
popup.html
<div class="pop-up-back" ng-click="hidePopup()" ng-class="{visible: visible}"></div>
<div class="pop-up" ng-class="{visible: visible}" ng-switch="type">
<bit-false-positive-popup ng-switch-when="falsePositive" type="type" action="action" data="data"></bit-false-positive-popup>
</div>
popup.js
var app = require('ui/modules').get('apps/bitsensor/popup');
app.directive('bitPopup', function () {
return {
restrict: 'E',
template: html,
scope: {
type: '=',
action: '=',
data: '='
},
controller: function ($scope) {
$scope.visible = false;
$scope.$on('showPopup', function () {
console.log('$scope.data: ', $scope.data);
$scope.visible = true;
});
$scope.$on('hidePopup', function () {
hidePopup();
});
function hidePopup() {
$scope.visible = false;
}
$scope.hidePopup = hidePopup;
}
};
});
false_positives.js
var app = require('ui/modules').get('apps/bitsensor/falsePositives');
app.directive('bitFalsePositivePopup', function () {
return {
restrict: 'E',
template: html,
scope: {
type: '=',
action: '=',
data: '='
},
controller: function ($scope, objectTools, bitbrainTools, keyTools) {
function init() {
console.log('$scope.data # fp: ', $scope.data);
}
function hidePopup() {
$scope.data = null;
$scope.$emit('hidePopup');
}
$scope.$on('showPopup', function () {
init();
});
init();
$scope.hidePopup = hidePopup;
}
}
}
Without the rest of the code I can only guess: You either need to use a promise when displaying the popup or use the $apply service to make the change to the popup visibility.
surround your $broadcast event in $timeout like follow:
$timeout(function() {
$broadcast('eventName');
});
It will wait for $scope update and then will trigger the event.
I am working on an angular project and I use a directive to create an isolated scope. The directive looks like this:
var directive = module.directive('question', function () {
return {
restrict: 'E',
templateUrl: 'question.html',
transclude: true,
scope: {
quiz: '=quiz'
},
link: function (scope, attr, element) {
scope.$watch(function () {
return scope.quiz;
},
function (oldVal, newVal) {
scope.currentQuestion = scope.quiz;
});
}
};
});
For I do not want to bind to a property (or field) in my Controller, I created a function and call the directive this way:
<question quiz="quiz.getCurrentQuestion()">... (transcluding stuff)</question>
Please note that quiz is my Controller using the as-Syntax.
The way I process the directive is working, but I don't like to create a two-way-binding ( to an R-value?).
Now I tried to just pass the function using &-binding but this just turns out odd results in the link-function and breaks everything.
Can I use the function-binding using & and somehow call the function (in my template or in the link-function) to get the result I need to make it work like two-way-binding?
Thank you for your help.
EDIT
The return value of the getCurrentQuestion-function is an object which looks like
{
questionNumber: 1,
answers: [],
getQuestionText() : function(...),
...
}
So nothing to special, I hope...
EDIT 2
When I use
...
scope: {
quiz: '&quiz'
}
then in the $watch-function I get
function(locals) { return parentGet(scope, locals); } for scope.quiz
And if I call the function like scope.quiz() I get undefined as result.
Couldn't find any way to watch a function in scope binding. However, there are other solutions. If you want single way binding you can use '#', but that means that you would have to parse the JSON in the watch ( working example):
var directive = module.directive('question', function () {
return {
restrict: 'E',
templateUrl: 'question.html',
transclude: true,
scope: {
quiz: '#'
},
link: function (scope, attr, element) {
scope.$watch('quiz', function (newVal, oldVal) {
scope.currentQuestion = angular.fromJson(newVal);
});
}
};
});
It works, but if you have a high rate of updates, the overhead can be annoying. What I would do, is use a service that holds all the questions, and both controller and directive can talk to. When the current question is changed, the controller should pass to the directive only the id of the new question (using simple # bind), and the directive would query the service for the question.
I am building a search application.I am using the highlighter function from Johann Burkard's JavaScript text higlighting jQuery plugin.
After an angularJS $Http call all the data is bound. I created a directive to call the Highlighter function.
searchApplication.directive('highlighter', function () {
return {
restrict: 'A',
link: function (scope, element) {
element.highlight(scope.searchText);
}
}
});
here is the controller
`searchApplication.controller('searchController', function ($scope, $http, searchService) {
$scope.myObject= {
searchResults: null,
searchText: "",
};
$scope.search = function () {
searchService.doSearch($scope.luceneSearch.searchText).then(
function (data) {
$scope.myObject.searchResults = data;
},
function (data, status, headers, configs) {
$log(status);
});
}
});`
here is the service
searchApplication.factory('searchService', function ($http, $q) {
return {
doSearch: function (_searchText) {
var deferred = $q.defer();
var searchURL = '/Search';
$http({
method: 'POST',
url: searchURL,
params: { searchText: _searchText }
})
.success(function (data, status, headers, configs) {
deferred.resolve(data);
})
.error(function (data, status, headers, configs) {
deferred.reject(data, status, headers, configs);
});
return deferred.promise;
}
}
});
In the html I have the following
<td ng-repeat="col in row.fields" highlighter>
{{col.Value}}
</td>
The directive is not getting the value to be searched, rather it gets {{col.Value}} and hence it is not able to highlight.
What am I doing wrong? How can I get the actual bound values so that I can manipulate it? Is there a better way to do this?
Updated: with controller and service code
From the code given, it should work fine if your controller has $scope.searchText properly set. I defined your directive in my app and debugged the link() function in Chrome, and scope.searchText is found as expected. If from browser debugging you find scope.searchText to be undefined you probably need to also post your controller code here.
UPDATE: From your comment, it seems the problem here is the execution order within Angular. Apparently the linking function is getting called before text interpolation is finished, so the solution is to wait for that process before proceeding with the highlighting part.
The trick here is to $watch for an update in col.Value and invoke the highlight logic afterward. The code below should do the trick:
app.directive('highlighter', function ($log) {
return {
restrict: 'A',
compile: function compile(element, attributes) {
// at this point, we still get {{col.Value}}
var preinterpolated = element.text().trim();
// removing the enclosing {{ and }}, so that we have just the property
var watched = preinterpolated.substring(2, preinterpolated.length - 2);
$log.info('Model property being watched: ' + watched);
return {
post: function (scope, element) {
// we watch for the model property here
scope.$watch(watched, function(newVal, oldVal) {
// when we reach here, text is already interpolated
$log.info(element.text());
element.highlight(scope.searchText);
});
}
};
}
};
});
This time around, $log logic should print out the interpolated value instead of just col.Value.
UPDATE2: I'm not quite sure how to go from there with directive, but if you don't mind using Angular filter, you can try this Angular UI solution. After attaching js file to your page, just include 'ui.highlight' module in your app and the filter will be available for use. It's also small like your jquery lib as well:
https://github.com/angular-ui/ui-utils/blob/master/modules/highlight/highlight.js
You can try live example here:
http://angular-ui.github.io/ui-utils/
Your HTML should now look like this (directive is no longer needed):
<td ng-repeat="col in row.fields">
{{col.Value | highlight:searchText}}
</td>
Also noted that now the CSS class for hightlighted text is ui-match instead of highlight.
UPDATE3: If you're set on using a directive, this person seems to do something very similar, except that he's adding a timeout:
http://dotnetspeak.com/2013/07/implementing-a-highlighting-directive-for-angular
setTimeout(function () {
element.highlight(scope.searchText);
}, 300);