Below Angular JS code works fine on Mouseover & Mouseout. Need help regard adding conditional logic on JS code.
If class name "active" exists, img src path have to be in "overImg" even if user mouseover & mouseout. But, present behaviour removes overImg once user mouseout from element. Active state have to be different from the rest of navigation element.
AngularJS:
.directive('eleHoverAction', function() {
return {
link: function (scope, elem, attrs) {
var imgObj = $(elem).find('img');
var upImg = attrs.eleUpImgSrc;
var overImg = attrs.eleOverImgSrc;
elem.bind('mouseover', function () {
$(imgObj).attr("src", overImg);
scope.$apply();
});
elem.bind('mouseout', function() {
$(imgObj).attr("src", upImg);
scope.$apply();
});
}
};
});
HTML:
<li class="menu-item menu-item--category active" ele-hover-action ele-up-img-src="images/test1.png" ele-over-img-src="images/test1-over.png">
<img src="images/test1.png" oversrc="images/test1-over.png" alt=""/><span>Test1</span>
</li>
<li class="menu-item menu-item--category" ele-hover-action ele-up-img-src="images/test2.png" ele-over-img-src="images/test2-over.png">
<img src="images/test2.png" oversrc="images/test2-over.png" alt=""/><span>Test2</span>
</li>
The most obvious method is to add an if statement to your "mouseout" handler that checks if the element hasClass active:
.directive('eleHoverAction', function() {
return {
link: function (scope, elem, attrs) {
var imgObj = elem.find('img');
var upImg = attrs.eleUpImgSrc;
var overImg = attrs.eleOverImgSrc;
elem.bind('mouseover', function () {
imgObj.attr("src", overImg);
scope.$apply();
});
elem.bind('mouseout', function() {
if (!elem.hasClass("active")) {
imgObj.attr("src", upImg);
}
scope.$apply();
});
if (elem.hasClass("active")) {
imgObj.attr("src", overImg);
} else {
imgObj.attr("src", upImg);
}
}
};
});
I went ahead and set the src attribute of the image based on the directive attributes. You could just take that part out if you don't want it. Also, wrapping elem in a jQuery call is redundant because Angular elements are already wrapped in either jQuery (if available when Angular loads) or its own jQLite. Otherwise, you wouldn't be able to call elem.bind.
Try it in a fiddle.
Related
I've been searching for an answer to simple but not trivial question: What is a right way to catch image' onload event in Angular only with jqLite? I found this question , but I want some solution with directives.
So as I said, this is not accepted for me:
.controller("MyCtrl", function($scope){
// ...
img.onload = function () {
// ...
}
because it is in controller, not in directive.
Here's a re-usable directive in the style of angular's inbuilt event handling directives:
angular.module('sbLoad', [])
.directive('sbLoad', ['$parse', function ($parse) {
return {
restrict: 'A',
link: function (scope, elem, attrs) {
var fn = $parse(attrs.sbLoad);
elem.on('load', function (event) {
scope.$apply(function() {
fn(scope, { $event: event });
});
});
}
};
}]);
When the img load event is fired the expression in the sb-load attribute is evaluated in the current scope along with the load event, passed in as $event. Here's how to use it:
HTML
<div ng-controller="MyCtrl">
<img sb-load="onImgLoad($event)">
</div>
JS
.controller("MyCtrl", function($scope){
// ...
$scope.onImgLoad = function (event) {
// ...
}
Note: "sb" is just the prefix I use for my custom directives.
Ok, jqLite' bind method doing well its job. It goes like this:
We are adding directive' name as attribute in our img tag . In my case , after loading and depending on its dimensions , image have to change its class name from "horizontal" to "vertical" , so directive's name will be "orientable" :
<img ng-src="image_path.jpg" class="horizontal" orientable />
And then we are creating simple directive like this:
var app = angular.module('myApp',[]);
app.directive('orientable', function () {
return {
link: function(scope, element, attrs) {
element.bind("load" , function(e){
// success, "onload" catched
// now we can do specific stuff:
if(this.naturalHeight > this.naturalWidth){
this.className = "vertical";
}
});
}
}
});
Example (explicit graphics!): http://jsfiddle.net/5nZYZ/63/
AngularJS V1.7.3 Added the ng-on-xxx directive:
<div ng-controller="MyCtrl">
<img ng-on-load="onImgLoad($event)">
</div>
AngularJS provides specific directives for many events, such as ngClick, so in most cases it is not necessary to use ngOn. However, AngularJS does not support all events and new events might be introduced in later DOM standards.
For more information, see AngularJS ng-on Directive API Reference.
I'm trying to create a simple html5 video playlist app. I've got an overlay div on top of the html5 video that should appear/disappear when stopping and starting the video.
I've got ng-show and a variable to trigger it, but it's not changing when I look using ng-inspector.
My events might not be quite correct, either - but I can't seem to find much information on putting events on different elements within the same directive. Is this a clue that I should break this up into multiple directives?
(function() {
'use strict';
angular
.module('app')
.controller('campaignController', campaignController)
.directive('myVideo', myvideo);
function campaignController($log,Campaign) {
var vm = this;
vm.overlay = true;
Campaign.getCampaign().success(function(data) {
vm.campaign = data[0];
vm.item = vm.campaign.videos[0];
});
vm.select = function(item) {
vm.item = item;
};
vm.isActive = function(item) {
return vm.item === item;
};
};
function myvideo() {
return {
restrict: 'E',
template: ['<div class="video-overlay" ng-show="vm.overlay">',
'<p>{{ vm.campaign.name}}</p>',
'<img class="start" src="play.png">',
'</div>',
'<video class="video1" controls ng-src="{{ vm.item.video_mp4_url | trusted }}" type="video/mp4"></source>',
'</video>' ].join(''),
link: function(scope, element, attrs) {
scope.video = angular.element(document.getElementsByClassName("video1")[0]);
scope.startbutton = angular.element(document.getElementsByClassName("start")[0]);
scope.startbutton.on('click', function() {
scope.vm.overlay = false;
scope.video[0].play();
});
scope.video.on('click', function() {
scope.video[0].pause();
scope.vm.overlay = true;
});
}
};
}
})();
From my personal experience angular expression evaluation does not work as javascript. so try ng-show="vm.overlay==true".
Furthermore you bind click using native javascript.
Either don't do that and use ng-click or call scope.$apply() in the click event t callbackas last intruction (even though i'm not sure if it's really important).
I'm trying to create a notification system in AngularJs just like the notification used here. When there is a new comment, answer, etc.. The archive icon shows a red sign with the number of activities, and when I click on it, it opens up a box with the last notifications.
To do this, I built this simple directive to dynamic loads a templateUrl:
html:
<li test-alert ref="msg">
<i class="fa fa-envelope-o"></i>
</li>
<li test-alert ref="bell">
<i class="fa fa-bell-o"></i>
</li>
directive:
angular
.module('agApp')
.directive('testAlert', testAlert)
;
/* #ngInject */
function testAlert() {
var templateA = '<div>Test template A</div>';
var templateB = '<div>Test template B</div>';
return{
restrict: 'A',
scope: {
ref: '#'
},
link: function(scope,element,attrs,controller){
scope.showAlert = false;
element.on("click", function() {
if (scope.ref == 'bell') {
scope.showAlert = true;
element.append(templateA);
scope.$apply();
} else {
scope.showAlert = true;
element.append(templateB);
scope.$apply();
};
console.log(scope.ref);
});
element.addEventListener("keyup", function(e) {
if (e.keyCode === 27) {
scope.showAlert = false;
}
});
}
};
}; //end test alert
But I'm with some problems..
If i click on the icon to open the template it will open, but every time i click on it, it will append another template. Id' like it to change (if it's the other template) or do nothing.
When it is opened, I can't make it close. I can use a 'close' button, but I'd like to close/remove the template when the user click on the document or press esc;
The code I tried to use to close on 'Esc' key, doesn't work.
My main objective is to create a notification system just like the one in stackOverflow, so is this the best way to do it? Should I use a controller instead?
Edit:
Close mechanism I'm using at the momment. It's working, but maybe it can be improved.
run.js
angular
.module('agApp')
.run(runApp);
/* #ngInject */
function runApp($rootScope) {
document.addEventListener("keyup", function(e) {
if (e.keyCode === 27) {
$rootScope.$broadcast("escapePressed", e.target);
};
});
document.addEventListener("click", function(e) {
$rootScope.$broadcast("documentClicked", e.target);
});
}; //end run
controller.js
$rootScope.$on("documentClicked", _close);
$rootScope.$on("escapePressed", _close);
function _close() {
$scope.$apply(function() {
vm.closeAlert();
});
};
Since I wasn't able to use it as a directive, I moved the open/close function inside a controller. But it can be used in any other way, as long as it works, there is no problem.
First off, key events only fire on the document and elements that may receive focus.
Directives are really nice for things you need to use multiple times. But even if you implement your notification system as a directive and only use it once - you will have it isolated, which is often good.
Hard to give the best solution without knowing more but here is one example that implements the messages and the notifications as one directive:
app.directive('notifications',
function() {
return {
restrict: 'E',
templateUrl: 'template.html',
scope: {},
link: function(scope, element, attrs, controller) {
scope.viewModel = {
showTemplateA: false,
showTemplateB: false
};
scope.toggleTemplateA = function() {
scope.viewModel.showTemplateA = !scope.viewModel.showTemplateA;
scope.viewModel.showTemplateB = false;
};
scope.toggleTemplateB = function() {
scope.viewModel.showTemplateB = !scope.viewModel.showTemplateB;
scope.viewModel.showTemplateA = false;
};
}
};
});
It simply contains logic for showing and hiding the templates. The directive uses a template that looks like this:
<div>
<i class="fa fa-envelope-o" ng-click="toggleTemplateA()"></i>
<div ng-show="viewModel.showTemplateA">
Template A
</div>
<br>
<i class="fa fa-bell-o" ng-click="toggleTemplateB()"></i>
<div ng-show="viewModel.showTemplateB">
Template B</div>
</div>
The template uses ng-show and ng-click to bind to our scope functions. This way we let Angular do the job and don't have to mess around with element.append etc.
Usage:
<notifications></notifications>
Demo: http://plnkr.co/edit/8M1D5uENjpDoIbb1ZuMR?p=preview
To implement your closing mechanism you can add the following to the directive:
var close = function () {
scope.$apply(function () {
scope.viewModel.showTemplateA = false;
scope.viewModel.showTemplateB = false;
});
};
$document.on('click', close);
$document.on('keyup', function (e) {
if (e.keyCode === 27) {
close();
}
});
scope.$on('$destroy', function () {
$document.off('click', close);
$document.off('keyup', close);
});
Note that you now have to inject $document into the directive:
app.directive('notifications', ['$document',
function($document) {
In the toggle functions you can call stopPropagation() to prevent the global closing handler to execute when you click the icons (might not be needed in this example, but good to know. Might want it on the actual templates in the future?):
scope.toggleTemplateA = function($event) {
$event.stopPropagation();
And:
ng-click="toggleTemplateA($event)"
Demo: http://plnkr.co/edit/LHS4RBE7qtY4yNyEdR16?p=preview
I'm monitoring a CSS style and updating a variable in the scope that's based on that CSS style's value. It works the first time around but when the browser is resized, the scope gets updated but ng-style does not update with the new scope parameter.
JS:
.directive('monitorStyle', function() {
return {
link: function(scope, element, attrs) {
scope[attrs.updateVariable] = $(element).css(attrs.monitorStyle);
angular.element(window).on('resize', function() {
scope[attrs.updateVariable] = $(element).css(attrs.monitorStyle);
});
}
}
})
HTML:
<p class="text" monitor-style="font-size" update-variable="textHeight">Press "<img class="mini up" src="img/select-arrow.png" src="Up" ng-style="{'height': textHeight}">
I'm trying to do this outside of the controller because that's what people recommend. Why is ng-style not updating when the scope gets updated?
The window event isn't an angular event, so angular don't know he have to update the model/scope. You have to add scope.$apply() to tell angular to refresh it :
angular.element(window).on('resize', function() {
scope[attrs.updateVariable] = $(element).css(attrs.monitorStyle);
scope.$apply();
});
Data bindig only works when your model is updated with angular event like $http, $timeout, ng-click, ...
A great article about it : http://jimhoskins.com/2012/12/17/angularjs-and-apply.html
Yo, I made this sick solution.
So if you want to watch styles (even an array of them on a particular element) and then send their values to the $scope you can use this JS:
.directive('monitorStyle', function($timeout) {
return {
link: function(scope, element, attrs) {
function addToScope() {
var updateVariable = attrs.updateVariable.split(',');
var monitorStyle = attrs.monitorStyle.split(',');
for (var i = 0; i < updateVariable.length; i++) {
scope[updateVariable[i]] = $(element).css(monitorStyle[i]);
}
}
addToScope();
angular.element(window).on('resize', function() {
addToScope();
scope.$apply();
});
}
}
})
And apply it like this:
<h2 monitor-style="font-size,line-height" update-variable="headerHeight,headerLineHeight">
This will update the $scope on initialization and on window resizes. You can of course modify it to your own purpose.
Each time the $scope changes you can update other styles like this:
<div ng-style="{'height': headerHeight, 'line-height': headerLineHeight}">
I have two controllers and share data between them with an app.factory function.
The first controller adds a widget in the model array (pluginsDisplayed) when a link is clicked. The widget is pushed into the array and this change is reflected into the view (that uses ng-repeat to show the array content):
<div ng-repeat="pluginD in pluginsDisplayed">
<div k2plugin pluginname="{{pluginD.name}}" pluginid="{{pluginD.id}}"></div>
</div>
The widget is built upon three directives, k2plugin, remove and resize. The remove directive adds a span to the template of the k2plugin directive. When said span is clicked, the right element into the shared array is deleted with Array.splice(). The shared array is correctly updated, but the change is not reflected in the view. However, when another element is added, after the remove, the view is refreshed correctly and the previously-deleted element is not shown.
What am I getting wrong? Could you explain me why this doesn't work?
Is there a better way to do what I'm trying to do with AngularJS?
This is my index.html:
<!doctype html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.min.js">
</script>
<script src="main.js"></script>
</head>
<body>
<div ng-app="livePlugins">
<div ng-controller="pluginlistctrl">
<span>Add one of {{pluginList.length}} plugins</span>
<li ng-repeat="plugin in pluginList">
<span>{{plugin.name}}</span>
</li>
</div>
<div ng-controller="k2ctrl">
<div ng-repeat="pluginD in pluginsDisplayed">
<div k2plugin pluginname="{{pluginD.name}}" pluginid="{{pluginD.id}}"></div>
</div>
</div>
</div>
</body>
</html>
This is my main.js:
var app = angular.module ("livePlugins",[]);
app.factory('Data', function () {
return {pluginsDisplayed: []};
});
app.controller ("pluginlistctrl", function ($scope, Data) {
$scope.pluginList = [{name: "plugin1"}, {name:"plugin2"}, {name:"plugin3"}];
$scope.add = function () {
console.log ("Called add on", this.plugin.name, this.pluginList);
var newPlugin = {};
newPlugin.id = this.plugin.name + '_' + (new Date()).getTime();
newPlugin.name = this.plugin.name;
Data.pluginsDisplayed.push (newPlugin);
}
})
app.controller ("k2ctrl", function ($scope, Data) {
$scope.pluginsDisplayed = Data.pluginsDisplayed;
$scope.remove = function (element) {
console.log ("Called remove on ", this.pluginid, element);
var len = $scope.pluginsDisplayed.length;
var index = -1;
// Find the element in the array
for (var i = 0; i < len; i += 1) {
if ($scope.pluginsDisplayed[i].id === this.pluginid) {
index = i;
break;
}
}
// Remove the element
if (index !== -1) {
console.log ("removing the element from the array, index: ", index);
$scope.pluginsDisplayed.splice(index,1);
}
}
$scope.resize = function () {
console.log ("Called resize on ", this.pluginid);
}
})
app.directive("k2plugin", function () {
return {
restrict: "A",
scope: true,
link: function (scope, elements, attrs) {
console.log ("creating plugin");
// This won't work immediately. Attribute pluginname will be undefined
// as soon as this is called.
scope.pluginname = "Loading...";
scope.pluginid = attrs.pluginid;
// Observe changes to interpolated attribute
attrs.$observe('pluginname', function(value) {
console.log('pluginname has changed value to ' + value);
scope.pluginname = attrs.pluginname;
});
// Observe changes to interpolated attribute
attrs.$observe('pluginid', function(value) {
console.log('pluginid has changed value to ' + value);
scope.pluginid = attrs.pluginid;
});
},
template: "<div>{{pluginname}} <span resize>_</span> <span remove>X</span>" +
"<div>Plugin DIV</div>" +
"</div>",
replace: true
};
});
app.directive("remove", function () {
return function (scope, element, attrs) {
element.bind ("mousedown", function () {
scope.remove(element);
})
};
});
app.directive("resize", function () {
return function (scope, element, attrs) {
element.bind ("mousedown", function () {
scope.resize(element);
})
};
});
Whenever you do some form of operation outside of AngularJS, such as doing an Ajax call with jQuery, or binding an event to an element like you have here you need to let AngularJS know to update itself. Here is the code change you need to do:
app.directive("remove", function () {
return function (scope, element, attrs) {
element.bind ("mousedown", function () {
scope.remove(element);
scope.$apply();
})
};
});
app.directive("resize", function () {
return function (scope, element, attrs) {
element.bind ("mousedown", function () {
scope.resize(element);
scope.$apply();
})
};
});
Here is the documentation on it: https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$apply
If you add a $scope.$apply(); right after $scope.pluginsDisplayed.splice(index,1); then it works.
I am not sure why this is happening, but basically when AngularJS doesn't know that the $scope has changed, it requires to call $apply manually. I am also new to AngularJS so cannot explain this better. I need too look more into it.
I found this awesome article that explains it quite properly.
Note: I think it might be better to use ng-click (docs) rather than binding to "mousedown". I wrote a simple app here (http://avinash.me/losh, source http://github.com/hardfire/losh) based on AngularJS. It is not very clean, but it might be of help.
I had the same issue. The problem was because 'ng-controller' was defined twice (in routing and also in the HTML).
Remove "track by index" from the ng-repeat and it would refresh the DOM
There's an easy way to do that. Very easy. Since I noticed that
$scope.yourModel = [];
removes all $scope.yourModel array list you can do like this
function deleteAnObjectByKey(objects, key) {
var clonedObjects = Object.assign({}, objects);
for (var x in clonedObjects)
if (clonedObjects.hasOwnProperty(x))
if (clonedObjects[x].id == key)
delete clonedObjects[x];
$scope.yourModel = clonedObjects;
}
The $scope.yourModel will be updated with the clonedObjects.
Hope that helps.