Angularjs directive for mouse over an elment - javascript

I'm making a profile page for a user. I want to make an inline editable element. Basically when a user move mouse over and element a little pencil icon is shown. Here is what I have so far:
angular.module....yada yada
.directive('editIcon', function () {
return {
restrict: 'A',
link: function($scope, element, attrs) {
var elemIcon = angular.element('<i class="glyphicon glyphicon-pencil"></i>');
var prevDisplay = elemIcon.css('display', 'none');
element.append(elemIcon);
element.on('mouseenter', function() {
elemIcon.css('display', 'inline');
});
element.on('mouseleave', function() {
elemIcon.css('display', 'none');
});
}
};
});
an just add edit-icon to your element. It works, but I'm an angular newbe and a new naming conventions are completely different*. Is the right approach? Should I use template or compile?
tiny rant about broken angular sematics :)
* why restrict: 'A' not 'Attribute' or at least 'Attr' ? save a few bytes?
* transclusion? really? why not inject or something similar
* services are not really services

why restrict: 'A'?
So that the compiler will not waste time looking for elements, CSS classes with this name I guess.
Is the right approach?
If it works :) Showing an element on hover can be accomplished with CSS3/HTML5, if you (and your clients) are on modern browsers.
Should I use template or compile?
Again, since it works, there is no need do make things more complex.

Related

What is the correct way to watch an element's height in AngularJS 1.4.8?

I realize that there are SO posts out there asking similar or even the same thing, but before you mash the duplicate button, be aware I have tried the solution from every single one, and none of them have worked in my target AngularJS version (1.4.8). With that in mind:
HTML:
<div ng-app="app" ng-controller="app">
<textarea size-watch>{{ height }}</textarea>
</div>
JavaScript:
angular.module("app", [])
.controller("app", function() {
})
.directive("sizeWatch", function() {
return {
restrict: "A",
link: function(scope, elem) {
scope.$watch(function() { return elem[0].offsetHeight; }, function(newHeight, oldHeight) {
scope.height = newHeight;
}, true);
}
}
});
Fiddle.
Here's what I'm trying to do: I want to create a directive that watches the height of an element and updates a property on the scope whenever it changes. I can get the value to initially display, so I know the link between the directive and the controller/scope is working. However, I can't get the value to update when the textarea is resized (by dragging the bottom right corner). I put a call to debugger; in the second watch function, and verified that it's only getting called once.
Feel free to update the linked fiddle when you find a working solution.
My choice of element for testing purposes was unfortunate. Apparently textarea doesn't raise resize events the same way as other elements (specifically, like divs). Here's a working fiddle of what I was trying to achieve.

Clicking on element not hit angularjs directive link function

It seems my li elements in angularjs directive not responding clicking event.
HTML:
<my-selbg>
<ul>
<li ng-repeat="bgimage in bgimages"><img src={{bgimage}} width="85" height="82" dir={{bgimage}}></li>
</ul>
</my-selbg>
JS:
var mlwcApp = angular.module('mlwcApp', [])
.controller('BgImagesListController', function($scope, $http) {
$http.get("http://localhost:8080/webcontent/bg_images").success(function(response) {
$scope.bgimages = response;
});
})
.directive('myselbg', function(){
return {
restrict: 'E',
scope: true,
link: function(scope, element, attrs){
var elementOne = angular.element(element.children[1]);
var elementTwo = angular.element(element.children[2]);
var elementThree = angular.element(element.children[3]);
setUpBGImg = function(){
console.log('link function');
};
$(elementOne).on('click', setUpBGImg);
$(elementTwo).on('click', setUpBGImg);
$(elementThree).on('click', setUpBGImg);
}
};
});
I have 3 li elements and clicking any of them dose not hit the code in link function. Anyone has idea?
You're new to angular, by the looks of it.
First off, before going any further - your directive will not even bind at all in the state it is in. You've got an element directive (which is fine, though if I were you I'd make it an attribute directive by restricting on A, which allows you to then apply it to the list rather than an element above it) named myselbg in your code. However, your markup is set as my-selbg, which would then look for the angular directive mySelbg, which does not exist.
In addition to this, your directive will evaluate before the list is rendered (thanks to the order of priority in execution). You have two choices to go around this:
You can do something like this: https://jsfiddle.net/a01n3srw/1/ . Really not recommended - I am using $timeout in order to evaluate code after the current refresh cycle is done, at which point the list fully exists
You can use the simple ngClick angular core directive in order to make this easy. Added bonus, when your function that you evaluate starts modifying scope, you won't shoot yourself in the foot using the previous method and having to use $apply

AngularJS wait for tag to load into DOM

I'm adding a google chart via the angular directive into a page and I would like to add an attribute to the element it creates after it's loaded. What is the best way to ensure the element exists before attempting to add the attribute?
From looking around it seems like my directive that I have SHOULD work but does not:
.directive('vdfWidgetGoogleChart', ['$timeout', function ($timeout) {
return {
restrict: 'E',
//replace: true,
templateUrl: 'widgetgooglechart.html',
link: function ($scope, elem, attrs) {
function addTabIndex () {
elem.find('svg').attr({tabindex: -1});
}
$timeout(addTabIndex);
},
scope: {
chartObject: '='
}
}
Personally when doing charting the easiest thing is to append the attribute to the SVG element, then you're being very angular as you aren't looking for elements.
Another option is create a controller and then a directive for the element SVG that requires that controller. Then when you have SVG as a child of that directive your svg directive should get called. (This is a guess I haven't tried it)
<div controller-directive=".."><svg></svg></div>
Then your code would have the SVG when the controller exists as a parent.
Or you can simply adjust your code to look for the svg every 100ms or so until you find it.
function addTabIndex () {
var svg = elem.find('svg');
if (svg.length != 0)
svg.attr({tabindex: -1});
else
$timeout(addTabIndex, 100);
}
$timeout(addTabIndex);

"perfect-scrollbar" (jQuery plugin) isn't initialised properly when a its container is being filled with Angular.js

On my page I programmatically generate a food/drinks menus from a json file with Angular.js. The problem is with the "perfect-scrollbar" used for scrolling the angular-generated content, which appears to require a scroll-wheel event to initialise on these menus. This makes it impossible to scroll on devices without a scroll-wheel. Apart from the angular-generated content other pages initialize perfect-scrollbar properly. This gave me a clue that the problem might lie with the interaction between jQuery world (perfect-scrollbar is a jQuery plugin) and Angular world.
The site is themockingbird.co.uk - navigate to the "Food" and "Drinks" to see the problem in action - can't scroll the content (the perfect-scrollbar won't appear) unless with the mouse scroll-wheel.
I've written this little directive:
mainMenuApp.directive('scrollBar', function(){
return {
restrict: 'C',
template: '<div ng-transclude></div>',
transclude: true,
scope: {},
link: function(scope, element, attrs){
$(element).perfectScrollbar();
//element.perfectScrollbar(); - doesn't work
//angular.element(element).perfectScrollbar(); - doesn't work
}
}
});
to facilitate the "perfect-scrollbar" via angular for the two menus, but this did not solve the problem.
How can I make the perfect-scrollbar work perfectly with angular (pun intended :)?
I appreciate your time.
cheers
Jared
At the time your link function is executed, your menu-food.json and menu-drink.json are not arrived yet and perfectScrollbar needs an update at the time the data arrive, called with:
$(element).perfectScrollbar('update');
Since you have no architecture for handling the food and drinks lists as decoupled watchable values in a controller attached by your directive, you may simply broadcast an event from the root scope, listened by your directive link functions, thus updating the perfectScrollbar instance at the right moment.
I had the same problem when using https://github.com/itsdrewmiller/angular-perfect-scrollbar - the following "add-on directive", solved this problem:
.directive('psMouseOver', function () {
return {
link: function(scope, element) {
element.bind("mouseover", function(e){
e.stopPropagation();
e.preventDefault();
element.perfectScrollbar('update');
});
}
}
});
In your case however, one would just add those lines and write your directive as:
mainMenuApp.directive('scrollBar', function(){
return {
restrict: 'A',
template: '<div ng-transclude></div>',
transclude: true,
scope: {},
link: function(scope, element, attrs){
element.perfectScrollbar();
element.bind("mouseover", function(e){
e.stopPropagation();
e.preventDefault();
element.perfectScrollbar('update');
});
}
}
});
That happen in my case when Angular.js files loaded before JQuery and Perfect Scrollbar scripts files includes.
<script src="assets/libs/jquery.min.js"></script>
<script src="assets/libs/jquery.mousewheel.js"></script>
<script src="assets/libs/perfect-scrollbar.js"></script>
Try to load firstly JQuery, Perfect Scrollbar, and just after AngularJS.

Conditionally animating ng-view transitions

I am trying to apply animations to ng-view (routing) depending of the views involved.
For example, from View1 to View2 I need the View1 leaving through the left side and View1 entering from the right side. Otherwise, from View2 to View1 I need View2 leaving through the right side and View1 entering from the left side.
But I have also situations where I need apply different animations to both views, for example, View1 leaving fading out and View2 entering scaling up.
What I am doing is using a scope associated variable as class in the ng-view:
<div ng-view class="{{transition}}"></div>
This variable is set in each route change with something like this in each controller:
$scope.transition=Global.transition;
$rootScope.$on("$routeChangeStart",function (event, current, previous) {
// Here I get the leaving view and the entering view and the kind of transition is selected
...
$scope.transition=selectedLeavingTransition; // Set the transition for the leaving view
Global.transition=selectedEnteringTransition; // Set the transition for the entering view
});
Global is a service to set the transition variable for the entering scope from the leaving scope.
This way, when a route change is detected, the current ng-view is set with the class associated to selectedLeavingTransition, and the entering ng-view is set with the class associated to selectedEnteringTransition.
For example, if the route change was from View1 to View2 the ng-views during the animation could be:
<div ng-view class="fadeOut ng-animate ng-leave ng-leave-active"></div>
<div ng-view class="scaleUp ng-animate ng-enter ng-enter-active"></div>
The CSS in this case could be:
fadeOut.ng-leave {animation:1s fadeOut;}
scaleUp.ng-enter {animation:1s scaleUp;}
Though it works, I am wondering if there is a simpler way to do it as it seems a little mess.
An alternative solution that doesn't require much code is to define your animations on your routes:
$routeProvider.when('/view1', {
templateUrl: 'view1.html',
controller: 'View1Controller',
animations: {
enter: 'enter-left',
leave: 'leave-left'
}
});
Then use a directive to retrieve the current route's animations and add them to the element:
app.directive('viewAnimations', function ($route) {
return {
restrict: 'A',
link: function (scope, element) {
var animations = $route.current.animations;
if (!animations) return;
if (animations.enter) element.addClass(animations.enter);
if (animations.leave) element.addClass(animations.leave);
}
};
});
And put the directive on the element that contains the ngView directive:
<body ng-view view-animations></body>
Demo: http://plnkr.co/edit/Y3ExDyiPIJwvVKO4njBT?p=preview
Edit: New solution.
To set animations during run-time I would use a service just like you are doing, but a directive to apply them.
Very basic example of service:
app.factory('viewAnimationsService', function ($rootScope) {
var enterAnimation;
var getEnterAnimation = function () {
return enterAnimation;
};
var setEnterAnimation = function (animation) {
enterAnimation = animation;
};
var setLeaveAnimation = function (animation) {
$rootScope.$emit('event:newLeaveAnimation', animation);
};
return {
getEnterAnimation: getEnterAnimation,
setEnterAnimation: setEnterAnimation,
setLeaveAnimation: setLeaveAnimation
};
});
And the directive:
app.directive('viewAnimations', function (viewAnimationsService, $rootScope) {
return {
restrict: 'A',
link: function (scope, element) {
var previousEnter, previousLeave;
var enterAnimation = viewAnimationsService.getEnterAnimation();
if (enterAnimation) {
if (previousEnter) element.removeClass(previousEnter);
previousEnter = enterAnimation;
element.addClass(enterAnimation);
}
$rootScope.$on('event:newLeaveAnimation', function (event, leaveAnimation) {
if (previousLeave) element.removeClass(previousLeave);
previousLeave = leaveAnimation;
element.addClass(leaveAnimation);
});
}
};
});
Demo: http://plnkr.co/edit/DuQXaN2eYgtZ725Zqzeu?p=preview
I have been working on it and I have a neater solution, what I was doing had some problems. Now I am just using the $routeChangeStart at root scope and selecting there the leaving and enter transitions.
The only problem I have is that on the routeChangeStart event I can't modify the leaving view so I can't establish the leaving transition to the ngView element class attribute. I had to set it directly through the DOM (I know that is bad practice).
I tried to modify the leaving view through a shared service, the root scope and $apply() but none of them worked. Once the routeChangeStart event is launched the view seems static.
Here is a working example: jsfiddle.net/isidrogarcia/Fs5NZ

Categories

Resources