i m trying to implement a dropdown on right click using this directive contextMenu in angularjs. This directive works fine in firefox but doesnt close the old menu while opening a different one when using ' google chrome'. Any idea how can i make changes to it.
Here is the plunkr
(function(angular) {
var ngContextMenu = angular.module('directive.contextMenu', []);
ngContextMenu.directive('cellHighlight', function() {
return {
restrict: 'C',
link: function postLink(scope, iElement, iAttrs) {
iElement.find('td')
.mouseover(function() {
$(this).parent('tr').css('opacity', '0.7');
}).mouseout(function() {
$(this).parent('tr').css('opacity', '1.0');
});
}
};
});
ngContextMenu.directive('context', [
function() {
return {
restrict: 'A',
scope: '#&',
compile: function compile(tElement, tAttrs, transclude) {
return {
post: function postLink(scope, iElement, iAttrs, controller) {
var ul = $('#' + iAttrs.context),
last = null;
ul.css({
'display': 'none'
});
$(iElement).bind('contextmenu', function(event) {
event.preventDefault();
ul.css({
position: "fixed",
display: "block",
left: event.clientX + 'px',
top: event.clientY + 'px'
});
last = event.timeStamp;
});
//$(iElement).click(function(event) {
// ul.css({
// position: "fixed",
// display: "block",
// left: event.clientX + 'px',
// top: event.clientY + 'px'
// });
// last = event.timeStamp;
//});
$(document).click(function(event) {
var target = $(event.target);
if (!target.is(".popover") && !target.parents().is(".popover")) {
if (last === event.timeStamp)
return;
ul.css({
'display': 'none'
});
}
});
}
};
}
};
}
]);
})(window.angular);
Change the .click event to .mouseup event and it will work with chrome.
$(document).mouseup(function(event) {
var target = $(event.target);
if (!target.is(".popover") && !target.parents().is(".popover")) {
if (last === event.timeStamp)
return;
ul.css({
'display': 'none'
});
}
});
I faced the same problem, and it worked for me. :)
Looking at the source code (of the directive), I think this context menu directive is a little bit too simple. It simply doesn't do a whole lot more than triggering a show/hide on the element referenced by the context attribute. It may have been enough for the use case of the one who wrote it, but it appears to be too lightweight for a general solution.
What is happening in the directive code: If you happened to trigger a context menu on the same row (or more general reference the same context menu) it works correctly because it will simply show the current context menu at a different place. If you trigger context1 first and then (by clicking on the second row) trigger a different context menu context2 there simply isn't any code that would trigger a hide of the context1 context menu.
You could implement this yourself as well but then keep track of any already opened context menu's and close them before another one is opened.
Btw: this context menu doesn't work for me in Firefox (38, Mac OS X) either. It opens the context menu and immediately closes it again. This is probably because both the contextmenu (on the table row) and the click (on document) are triggered.
Related
I have a call stack like below and problem is that directive template is not interpolated. So as a result of this I can see {{ data | json }} as a string and ng-repeat is not triggered. How to approach this?
Context of situation is that I have a Highchart's chart where I need to provide clickable plot lines. On line click I need to display popover with dynamic content.
Optional question to answer:
My play with events is working well but I'm not sure if it's also well done. I would welcome any criticism on that. Idea is to hide popover on all following clicks.
Code:
1.
series: {events: {click: function(e) {drillDownCall(e, dataGroups)}
2.
function drillDownCall (e, dataGroups) {
var elem = angular.element('#drilldown');
if (!elem[0]) {
elem = angular.element('<drilldown fancy-name="dataGroups"></drilldown>');
}
elem.css({
position: 'absolute',
top: e.pageY,
left: e.pageX,
width: '150px',
height: '250px',
zIndex: '2000',
background: 'red'
});
var body = angular.element(document).find('body').eq(0);
var scope = $rootScope.$new();
scope.dataGroups = dataGroups;
body.append($compile(elem)(scope));
}
3.
.directive('drilldown', [
'$compile',
'$window',
function (
$compile,
$window
) {
return {
restrict: 'E',
replace: true,
scope: {
data: '=fancyName'
},
template: '' +
'<div id="drilldown">{{ data | json }}' +
'<ul>' +
'<li ng-repeat="group in data">{{ group.name }}</li>' +
'</ul>' +
'</div>',
link: function (scope, element) {
var ele = $compile(element)(scope),
off;
angular.element($window).on('click', function(e) {
scope.$emit('drilldown::click');
});
off = scope.$on('drilldown::click', function() {
angular.element(ele).remove();
angular.element($window).off('click');
off();
});
}
};
}]
)
I am unable test it myself but I think I know why.
Start of everything is drillDownCall and it is called by an event that is outside of Angular.js digest cycle. So Angular.js has no idea that there is a change in scope, and doesn't run a digest cycle, causing the new directive appear as non-compiled bunch of strings. (yes even you used $compile it works like that)
In summary, if I remember correct, you need at least one digest cycle to see that directive compiled. To trigger a digest cycle, you can add
$rootScope.$apply() or $rootScope.$applyAsync() or anything equivalent to it to the end of drillDownCall event handler.
Can you please try this?
I'm using state router to transition between pages.
I need to add a class to the <body> while the animation is running and remove it once the enter and leave animations are completed.
I tried to create a directive an inject the $animate service.
Then I started listening for enter and leave events as suggest in documentation.
The html:
<div class="ui-view-container">
<div ui-view style="height:100%;" class="suffle-page" suffle-page></div>
</div>
The directive:
;(function(){
angular.module('app')
.directive('sufflePage',function($animate){
var $body = $('body');
return {
link: function (scope, element) {
//var $el = $('[ui-view]');
$animate.enter(element,
function callback(element, phase) {
//$body.addClass('animating');
}
);
$animate.leave( element, function(){
function callback(element, phase) {
//$body.removeClass('animating')
}
})
}
}
});
})();
Then I have the CSS that animates those views
//prevents animation in mobile devices to faster performance
.ui-view-container {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: hidden;
}
[ui-view].ng-enter, [ui-view].ng-leave {
...
}
[ui-view].ng-enter {
..
}
[ui-view].ng-enter-active {
..
}
[ui-view].ng-leave {
...
}
[ui-view].ng-leave-active {
...
}
body.animating{
/*this element is outter of the animation that's why i must append a class to the top level element. in this case body*/
.special-element{
display: none;
}
}
At $animate.enter(element...) an error is thrown:
TypeError: Cannot read property 'createDocumentFragment' of null
Any help?
I was misunderstanding the use of $animate.enter and $animate.leave and **I also did use an incorrect version of angular because the $animate.leave are part of 1.4.x versions an my project was built on top of version 1.3.0.
After updating the angular.js and angular-animate.js all i had to do was
1) create the directive that will monitor enter:start and enter:end events
2) load the directive into the project
3) and write the piece of code that adds the class to the body during the animation.
I hope it helps.
.directive('sufflePage',function($animate){
var $body = $('body');
return {
link: function (scope, element) {
if (!element){
return;
}
/***
* when the ui-view that is `entering` the page stars it adds the animating class to body
* when it leaves it removes the animating from the body class
*
* IMPORTANT: this works because the enter and exit animation are triggered in parallel with the same duration
*
*/
$animate.on('enter', element,
function callback(element, phase) {
if (phase == 'start'){
$body.addClass('animating');
} else {
$body.removeClass('animating');
}
}
);
scope.$on('$destroy', function(){
$animate.off('enter',element);
});
}
}
I have set up an app with angular.js. I am creating a menu via ng-repeat. Every link calls a function inside a directive that uses paper.js to draw text to canvas.
But canvas not updates until i move the mouse around browser. The function that draws on to the canvas is calling immediately, but the canvas shows nothing without mouse moves away from the link?
angular.js controller that calls draw function inside directive:
.controller('NavCtrl', function($scope, $location, $http, WorkService) {
$scope.works = [];
$http({method: 'GET', url: '/api/v1/work'}). //collects all works
success(function(data, status, headers, config) {
$scope.works = data.objects;
});
$scope.setTitle = function(work) {
$scope.currentTitle=work.title;
$scope.writeTitle(work.title);
};
})
angular.js directive:
.directive('draw', function () {
return {
restrict: 'A',
link: function postLink($scope, element, attrs) {
$scope.writeTitle = function(inText){
var letters = inText.split('');
for(var i=0; i<2; i++){
var text = new PointText(new Point(getRandom(0, 200), getRandom(0, 100)));
text.content = letters[i];
text.style = {
fontFamily: 'Arial',
fontWeight: 'normal',
fontSize: 14,
fillColor: 'red',
justification: 'center'
};
}
}
function getRandom(min, max) {
return Math.random() * (max - min) + min;
}
function initPaper() {
paper.install(window);
paper.setup('canvas');
}
initPaper();
}
};
});
the menu in my base.html that calls controller function to draw:
<div ng-controller=NavCtrl>
<nav>
<a href='#/bilgi'>bilgi</a>
<a ng-click="setTitle(work)" href='#/ardiye/{{work.id}}' ng-repeat='work in works'>{{$index+1}}</a>
</nav>
</div>
---- UPDATE ----
I have added:
<script type="text/paperscript" canvas="canvas">
function onFrame(event) {
}
</script>
this empty onFrame function to base.html and now the canvas updates normally. But why?
Have you tried updating the View?
I had an issue on Firefox and IE11 with the canvas not updating until a mouseover after I initiated some operation.
I just called paper.view.update() at the end of each function and it all went down allright.
If this is the issue, it has nothing to do with Angular
Wrap your code inside your directive function within a scope.$apply() function. This will make sure that it updates the UI.
One other thing, it is convention inside a directive to use to use scope (no dollar sign) instead of $scope. In a controller you are actually passing in the named object $scope. Angular needs to know the actual names of the objects you want to inject. Inside your directive's link function, however, the actual names do not matter. This is because inside link attribute the parameters are passed in a specific order: scope, element, attributes. You could use foo, bar, baz if you wanted but they will still be resolved as scope, element, atttributes.
With this in mind you could actually use $scope but realize that you can do that only because of the order it is inside the function parameter list.
link: function postLink(scope, element, attrs)
scope.writeTitle = function(inText){
scope.$apply(function () {
var letters = inText.split('');
for(var i=0; i<2; i++){
var text = new PointText(new Point(getRandom(0, 200), getRandom(0, 100)));
text.content = letters[i];
text.style = {
fontFamily: 'Arial',
fontWeight: 'normal',
fontSize: 14,
fillColor: 'red',
justification: 'center'
};
});
}
How do I enable/disable anchor tags using the directive approach?
Example:
while clicking on edit link, create & delete needs to be disabled or grayed out
while clicking on create link, edit & delete needs to be disabled or grayed out
JAVASCRIPT:
angular.module('ngApp', []).controller('ngCtrl',['$scope', function($scope){
$scope.create = function(){
console.log("inside create");
};
$scope.edit = function(){
console.log("inside edit");
};
$scope.delete = function(){
console.log("inside delete");
};
}]).directive('a', function() {
return {
restrict: 'E',
link: function(scope, elem, attrs) {
if(attrs.ngClick || attrs.href === '' || attrs.href === '#'){
elem.on('click', function(e){
e.preventDefault();
if(attrs.ngClick){
scope.$eval(attrs.ngClick);
}
});
}
}
};
});
LINK to CODE
Update:
Disabling the href works better in the link function return. Code below has been updated.
aDisabled naturally executes before ngClick because directives are sorted in alphabetical order. When aDisabled is renamed to tagDisabled, the directive does not work.
To "disable" the "a" tag, I'd want the following things:
href links not to be followed when clicked
ngClick events not to fire when clicked
styles changed by adding a disabled class
This directive does this by mimicking the ngDisabled directive. Based on the value of a-disabled directive, all of the above features are toggled.
myApp.directive('aDisabled', function() {
return {
compile: function(tElement, tAttrs, transclude) {
//Disable ngClick
tAttrs["ngClick"] = "!("+tAttrs["aDisabled"]+") && ("+tAttrs["ngClick"]+")";
//return a link function
return function (scope, iElement, iAttrs) {
//Toggle "disabled" to class when aDisabled becomes true
scope.$watch(iAttrs["aDisabled"], function(newValue) {
if (newValue !== undefined) {
iElement.toggleClass("disabled", newValue);
}
});
//Disable href on click
iElement.on("click", function(e) {
if (scope.$eval(iAttrs["aDisabled"])) {
e.preventDefault();
}
});
};
}
};
});
Here is a css style that might indicate a disabled tag:
a.disabled {
color: #AAAAAA;
cursor: default;
pointer-events: none;
text-decoration: none;
}
And here is the code in action, with your example
My problem was slightly different: I have anchor tags that define an href, and I want to use ng-disabled to prevent the link from going anywhere when clicked. The solution is to un-set the href when the link is disabled, like this:
<a ng-href="{{isDisabled ? '' : '#/foo'}}"
ng-disabled="isDisabled">Foo</a>
In this case, ng-disabled is only used for styling the element.
If you want to avoid using unofficial attributes, you'll need to style it yourself:
<style>
a.disabled {
color: #888;
}
</style>
<a ng-href="{{isDisabled ? '' : '#/foo'}}"
ng-class="{disabled: isDisabled}">Foo</a>
For people not wanting a complicated answer, I used Ng-If to solve this for something similar:
<div style="text-align: center;">
<a ng-if="ctrl.something != null" href="#" ng-click="ctrl.anchorClicked();">I'm An Anchor</a>
<span ng-if="ctrl.something == null">I'm just text</span>
</div>
Modifying #Nitin's answer to work with dynamic disabling:
angular.module('myApp').directive('a', function() {
return {
restrict: 'E',
link: function(scope, elem, attrs) {
elem.on('click', function(e) {
if (attrs.disabled) {
e.preventDefault(); // prevent link click
}
});
}
};
});
This checks the existence of disabled attribute and its value upon every click.
Disclaimer:
The OP has made this comment on another answer:
We can have ngDisabled for buttons or input tags; by using CSS we can
make the button to look like anchor tag but that doesn't help much! I
was more keen on looking how it can be done using directive approach
or angular way of doing it?
You can use a variable inside the scope of your controller to disable the links/buttons according to the last button/link that you've clicked on by using ng-click to set the variable at the correct value and ng-disabled to disable the button when needed according to the value in the variable.
I've updated your Plunker to give you an idea.
But basically, it's something like this:
<div>
<button ng-click="create()" ng-disabled="state === 'edit'">CREATE</button><br/>
<button ng-click="edit()" ng-disabled="state === 'create'">EDIT</button><br/>
<button href="" ng-click="delete()" ng-disabled="state === 'create' || state === 'edit'">DELETE</button>
</div>
Have you tried using lazy evaluation of expressions like disabled || someAction()?
Lets assume I defined something like so in my controller:
$scope.disabled = true;
Then I can disabling a link and apply inline styles like so:
<a data-ng-click="disabled || (GoTo('#/employer/'))" data-ng-style="disabled && { 'background-color': 'rgba(99, 99, 99, 0.5)', }">Higher Level</a>
Or better still disable a link and apply a class like so:
<a data-ng-click="disabled || (GoTo('#/employer/'))" data-ng-class="{ disabled: disabled }">Higher Level</a>
Note: that you will have a class="disabled" applied to DOM element by that statement.
At this stage you just need to handle what you action GoTo() will do. In my case its as simple as redirect to associated state:
$scope.GoTo = function (state) {
if (state != undefined && state.length > 0) {
$window.location.hash = state;
}
};
Rather than being limited by ngDisabled you are limited by what you decide to do.
With this technique I successfully applied permission level checking to enable or disable user access to certain part of my module.
Simple plunker to demonstrate the point
You can create a custom directive that is somehow similar to ng-disabled and disable a specific set of elements by:
watching the property changes of the custom directive, e.g. my-disabled.
clone the current element without the added event handlers.
add css properties to the cloned element and other attributes or event handlers that will
provide the disabled state of an element.
when changes are detected on the watched property, replace the current element with the cloned element.
HTML
<a my-disabled="disableCreate" href="#" ng-click="disableEdit = true">CREATE</a><br/>
<a my-disabled="disableEdit" href="#" ng-click="disableCreate = true">EDIT</a><br/>
<a my-disabled="disableCreate || disableEdit" href="#">DELETE</a><br/>
RESET
JAVASCRIPT
directive('myDisabled', function() {
return {
link: function(scope, elem, attr) {
var color = elem.css('color'),
textDecoration = elem.css('text-decoration'),
cursor = elem.css('cursor'),
// double negation for non-boolean attributes e.g. undefined
currentValue = !!scope.$eval(attr.myDisabled),
current = elem[0],
next = elem[0].cloneNode(true);
var nextElem = angular.element(next);
nextElem.on('click', function(e) {
e.preventDefault();
e.stopPropagation();
});
nextElem.css('color', 'gray');
nextElem.css('text-decoration', 'line-through');
nextElem.css('cursor', 'not-allowed');
nextElem.attr('tabindex', -1);
scope.$watch(attr.myDisabled, function(value) {
// double negation for non-boolean attributes e.g. undefined
value = !!value;
if(currentValue != value) {
currentValue = value;
current.parentNode.replaceChild(next, current);
var temp = current;
current = next;
next = temp;
}
})
}
}
});
Make a toggle function in the respective scope to grey out the link.
First,create the following CSS classes in your .css file.
.disabled {
pointer-events: none;
cursor: default;
}
.enabled {
pointer-events: visible;
cursor: auto;
}
Add a $scope.state and $scope.toggle variable. Edit your controller in the JS file like:
$scope.state='on';
$scope.toggle='enabled';
$scope.changeState = function () {
$scope.state = $scope.state === 'on' ? 'off' : 'on';
$scope.toggleEdit();
};
$scope.toggleEdit = function () {
if ($scope.state === 'on')
$scope.toggle = 'enabled';
else
$scope.toggle = 'disabled';
};
Now,in the HTML a tags edit as:
CREATE<br/>
EDIT<br/>
DELETE
To avoid the problem of the link disabling itself,
change the DOM CSS class at the end of the function.
document.getElementById("create").className = "enabled";
You may, redefine the a tag using angular directive:
angular.module('myApp').directive('a', function() {
return {
restrict: 'E',
link: function(scope, elem, attrs) {
if ('disabled' in attrs) {
elem.on('click', function(e) {
e.preventDefault(); // prevent link click
});
}
}
};
});
In html:
<a href="nextPage" disabled>Next</a>
I'd expect anchor tags to lead to a static page with a url. I think that a buttons suits more to your use case, and then you can use ngDisabled to disable it. From the docs: https://docs.angularjs.org/api/ng/directive/ngDisabled
ui-router v1.0.18 introduces support for ng-disabled on anchor tags
Example: <a ui-sref="go" ng-disabled="true">nogo</a>
https://github.com/angular-ui/ui-router/issues/2957
https://github.com/angular-ui/ui-router/pull/3692/commits/a59fcae300f9d8f73a5b91fa77c92b926e68281d
I want to change CSS elements while a user scrolls the angular way.
here's the code working the JQuery way
$(window).scroll(function() {
if ($(window).scrollTop() > 20 && $(window).scrollTop() < 600) {
$('header, h1, a, div, span, ul, li, nav').css('height','-=10px');
} else if ($(window).scrollTop() < 80) {
$('header, h1, a, div, span, ul, li, nav').css('height','100px');
}
I tried doing the Angular way with the following code, but the $scope.scroll seemed to be unable to properly pickup the scroll data.
forestboneApp.controller('MainCtrl', function($scope, $document) {
$scope.scroll = $($document).scroll();
$scope.$watch('scroll', function (newValue) {
console.log(newValue);
});
});
Remember, in Angular, DOM access should happen from within directives. Here's a simple directive that sets a variable based on the scrollTop of the window.
app.directive('scrollPosition', function($window) {
return {
scope: {
scroll: '=scrollPosition'
},
link: function(scope, element, attrs) {
var windowEl = angular.element($window);
var handler = function() {
scope.scroll = windowEl.scrollTop();
}
windowEl.on('scroll', scope.$apply.bind(scope, handler));
handler();
}
};
});
It's not apparent to me exactly what end result you're looking for, so here's a simple demo app that sets the height of an element to 1px if the window is scrolled down more than 50 pixels: http://jsfiddle.net/BinaryMuse/Z4VqP/