Change Text in directive - angularjs

How can I change something (for instance the color) in the directive?
I have a controller which gets after every 3 seconds new data (eye Positions)
myApp.controller('MainController', function ($scope, $interval, externalService, eyeTrackerService) {
$rootScope.gazePoints = [];
var size = 4;
var analyze = function () {
if ($rootScope.gazePoints.length > size) {
$scope.gazeArea = eyeTrackerService.getGazePoints($scope.gazePoints);
//reduce data in array
$scope.gazePoints.splice(0, 3);
}
};
var eyeTrackerData = function () {
externalService.getData().then(function (eyeTrackerData) {
var eyetracker = eyeTrackerData.data.EyeTracker;
var gaze_X = (eyetracker.X_left + eyetracker.X_right) * 0.5 * screen.availWidth;
var gaze_Y = (eyetracker.Y_left + eyetracker.Y_right) * 0.5 * screen.availHeight;
$scope.gazePoints.push({ x: gaze_X, y: gaze_Y });
analyze();
});
};
$interval(eyeTrackerData, 3000)
});
The service gets an array of gazePoints and have to analyse if the user is looking at the panel:
myApp.service('eyeTrackerService', function ($rootScope) {
this.getGazePoints = function (gazePoints) {
var counter = 0;
var date = $rootScope.rect;
for (var i = 0; i < gazePoints.length; i++) {
var x = gazePoints[i].x;
var y = gazePoints[i].y;
if (x >= date.left && x <= date.right && y >= date.top && y < date.bottom) {
counter =+ 1;
console.log("is watching");
if(counter == 5){
///Here it should call the directive and change the color
}
}
else {
console.log("is not watching")
}
}
}
});
my directive:
myApp.directive('dateInfo', function ($rootScope, $interval) {
return {
restrict: 'A',
scope: {
},
templateUrl: '123/Scripts/directives/html/dateInfo.html',
link: function (scope, element, attrs) {
$interval(function () {
$rootScope.rect = element[0].getBoundingClientRect();
//Here i want to change the color
//for example a scope.changetext(); but how??
}, 3000);
}
};
});
my html-file:
<div class="panel panel-primary">
<div class="panel-heading">MyPanel</div>
<div class="panel-body">
<input id="test" name="test">
</div>
</div>
I know it´s a lot of code. But as you can see the directive are also called after every second to get the current position of the panel.
Now I want to change the color in the panel, after the counter is == 5
What is the best solution in that case? I heard it not good to change the text in the controller.

Inside the interval, use
$interval(function () {
$rootScope.rect = element[0].getBoundingClientRect();
element.addClass('someClassThatSetsColor');
}, 3000);
You can set the class based on a parameter of your choice, say if it's not looking you'll set a class that changes the color to red, or whatever.

Related

How to show star-rating dynamically based on response?

I need to display star rating dynamically based on response.
I am able to display values from 1 to 5 but if rating is 0 then no empty stars are displaying.
If rating = 0.4 also it's showing 1 star filled.
My controller:
(function() {
'use strict';
angular
var app = angular
.module('app')
app.directive('starRating', function () {
return {
restrict: 'A',
template: '<ul class="rating">' +
'<li ng-repeat="star in stars" ng-class="star" ng-click="toggle($index)">' +
'\u2605' +
'</li>' +
'</ul>',
scope: {
ratingValue: '=',
max: '='
},
link: function (scope, elem, attrs) {
var updateStars = function () {
scope.stars = [];
for (var i = 0; i < scope.max; i++) {
if(i == 0) {
scope.stars = [];
scope.stars.push({
empty: i = 0
});
} else {
scope.stars.push({
filled: i < scope.ratingValue
});
}
}
};
scope.$watch('ratingValue', function (oldVal, newVal) {
if (newVal) {
updateStars();
}
});
}
}
});
app.controller('Controller', Controller);
Controller.$inject = ['UserService', '$location', '$rootScope', '$scope', 'fileUpload', 'FlashService', '$cookieStore', '$timeout', '$window'];
function Controller(UserService, $location, $rootScope, $scope, fileUpload, FlashService, $cookieStore, $timeout, $window) {
$scope.selectTestSubject = function() {
$scope.rating = 0;
console.log(levels.length);
for(var k=0; k<levels.length; k++) {
var respRating = levels[k].rating;
// var respRating = 1.5;
console.log(respRating);
$scope.ratings = [{
current: respRating,
max: 5
}];
if(respRating == 0) {
$scope.defaultRating = true;
} else {
$scope.defaultRating = false;
}
}
}
}
}) ();
My HTML page:
<div><span ng-repeat="rating in ratings">
<div star-rating rating-value="rating.current" max="rating.max"></div>
</span>
</div>
One problem with your solution is your $watch expression. Where you have the following:
scope.$watch('ratingValue', function (oldVal, newVal) {
if (newVal) {
updateStars();
}
});
oldVal and newVal are actually the wrong way around, the $watch function first takes in the new value followed by the old value. Secondly, the condition if (newVal) doesn't work for 0, because 0 is a falsey value.
Instead, you should have:
scope.$watch('ratingValue', function(value, previousValue) {
// Only update the view when the value has changed.
if (value !== previousValue) {
updateStars();
}
});
Your updateStars function also always reinitialises the scope.stars variable and appends onto it. Doing this can have some unwanted side effects and results in the view not reflecting the model value. It's best to initialise the array, then append the item if it doesn't yet exist or update the existing value. So you'll have something like this:
// Initialise the stars array.
scope.stars = [];
var updateStars = function() {
for (var i = 0; i < scope.max; i++) {
var filled = i < Math.round(scope.ratingValue);
// Check if the item in the stars array exists and
// append it, otherwise update it.
if (scope.stars[i] === undefined) {
scope.stars.push({
filled: filled
});
} else {
scope.stars[i].filled = filled;
}
}
};
Since the $watch expression only updates the stars when the value has changed, you'll now need to trigger the update the first time your link function fires. So this is simply:
// Trigger an update immediately.
updateStars();
Your template also does not correctly utilise the filled property on the star, it should instead contain the appropriate ng-class like so:
<ul class="rating">
<li class="star"
ng-repeat="star in stars"
ng-class="{ filled: star.filled }"
ng-click="toggle($index)">
\u2605
</li>
</ul>
With a simple style,
.star {
cursor: pointer;
color: black;
}
.star.filled {
color: yellow;
}
You can also improve this rating system by listening to mouseenter and mouseleave effects, so that the stars appear yellow when the user is selecting a new value. This is pretty common functionality. You can achieve this by making a few modifications.
To begin with, the template should be updated to listen for these events:
<ul class="rating">
<li class="star"
ng-repeat="star in stars"
ng-class="{ filled: star.filled }"
ng-mouseenter="onMouseEnter($event, $index + 1)"
ng-mouseleave="onMouseLeave($event)"
ng-click="toggle($index)">
\u2605
</li>
</ul>
Next, we want to make a small adjustment to the updateStars function to take in a rating parameter:
var updateStars = function(rating /* instead of blank */ ) {
for (var i = 0; i < scope.max; i++) {
var filled = i < Math.round(rating); // instead of scope.ratingValue
// Check if the item in the stars array exists and
// append it, otherwise update it.
if (scope.stars[i] === undefined) {
scope.stars.push({
filled: filled
});
} else {
scope.stars[i].filled = filled;
}
}
};
// Trigger an update immediately.
updateStars(scope.ratingValue /* instead of blank */ );
scope.$watch('ratingValue', function(value, previousValue) {
// Only update the view when the value changed.
if (value !== previousValue) {
updateStars(scope.ratingValue /* instead of blank */ );
}
});
Now we can add in our event callbacks from the view,
// Triggered when the cursor enters a star rating (li element).
scope.onMouseEnter = function (event, rating) {
updateStars(rating);
};
// Triggered when the cursor leaves a star rating.
scope.onMouseLeave = function (event) {
updateStars(scope.ratingValue);
};
And that's it! Full demo here.

Why does only the last of these duplicated directives work as intended?

I have a directive that needs to display certain numbers of images based on the width of the screen, however I have noticed that when repeating this directive using an ng-repeat only the last implementation of the directive works as intended. I think this may be something to do with calling $apply() but window re-sizes do not trigger digests so I need to in order to see the changes reflected on the page.
I'm not getting any errors in the console. Is there any way to solve this?
Here's the directive.
(function () {
'use strict';
angular.module('xxxx')
.directive('xxx', xxx);
function xxxxx ($window, $timeout) {
return {
restrict: 'AE',
templateUrl: 'xxxx',
scope :{
data : "="
},
link: function (scope, elem, attrs) {
scope.componentName = 'xxxxx';
var pageWidth = window.innerWidth;
scope.resultLimit = scope.data.conferences.length;
scope.setResultLimit = function(){
console.log('triggered');
pageWidth = window.innerWidth;
if(pageWidth < 768){
if(scope.data.conferences.length % 2 !== 0){
console.log('Less than 768');
scope.resultLimit = scope.data.conferences.length - 1;
} else{
scope.resultLimit = scope.data.conferences.length;
}
}
if(pageWidth >= 768 && pageWidth < 1200){
if(scope.data.conferences.length % 3 !== 0){
console.log('Greater than 768 and less than 1200');
scope.resultLimit = scope.data.conferences.length - (scope.data.conferences.length % 3);
} else{
scope.resultLimit = scope.data.conferences.length;
}
}
if(pageWidth >= 1200){
console.log('greater than 1200');
if(scope.data.conferences.length % 5 !== 0){
scope.resultLimit = scope.data.conferences.length - (scope.data.conferences.length % 5);
} else{
scope.resultLimit = scope.data.conferences.length;
}
}
console.log(scope.resultLimit);
//scope.$apply();
};
window.onresize = function(event){
scope.$apply(function(){
scope.setResultLimit();
})
};
scope.setResultLimit();
}
};
}
})();
You are overwriting onresize event handler (window.onresize property) in every next directive initialization. You need to bind several events with addEventListener method. So it will be:
window.addEventListener('resize', function() {
scope.$apply(function() {
scope.setResultLimit();
});
});

Angularjs responsive directive live updating issue (possibly due to ng-repeating the directive)

I am creating a post feed by ng-repeating JSON files from the cloud. I tried to make the posts responsive by using angular directives that update the template url with the screen size.
The problem is that only the last post in the ng-repeat responds and changes templates (with or without the reverse filter) when I resize the page. The other posts just remain the template that it was when originally loaded.
Here's the ng-repeat in the page
<div ng-show="post_loaded" ng-repeat="post in posts | reverse | filter:searchText ">
<feed-post>
</feed-post>
</div>
Here's the directive javascript file
app.directive('feedPost', function ($window) {
return {
restrict: 'E',
template: '<div ng-include="templateUrl"></div>',
link: function(scope) {
$window.onresize = function() {
changeTemplate();
scope.$apply();
};
changeTemplate();
function changeTemplate() {
var screenWidth = $window.innerWidth;
if (screenWidth < 768) {
scope.templateUrl = 'directives/post_mobile.html';
} else if (screenWidth >= 768) {
scope.templateUrl = 'directives/post_desktop.html';
}
}
}
};});
This happens because you re-assigning the .onresize in each directive and it stays effective only for the last linked directive.
I'd suggest to use it in a more angular way. You don't actually need a custom directive
In the controller that manages list of posts add reference to $window in $scope
$scope.window = $window;
Then in template make use of it
<div ng-include="directives/post_mobile.html" ng-if="window.innerWidth < 768"></div>
<div ng-include="directives/post_desktop.html" ng-if="window.innerWidth >= 768"></div>
To avoid extra wrappers for posts feed you might want to use ng-repeat-start, ng-repeat-end directives
this is a directive i wrote based on bootstrap sizes and ngIf directive :
mainApp.directive("responsive", function($window, $animate) {
return {
restrict: "A",
transclude: 'element',
terminal: true,
link: function($scope, $element, $attr, ctrl, $transclude) {
//var val = $attr["responsive"];
var block, childScope;
$scope.$watch(function(){ return $window.innerWidth; }, function (width) {
if (width < 768) {
var s = "xs";
} else if (width < 992) {
var s = "sm";
} else if (width < 1200) {
var s = "md";
} else {
var s = "lg";
}
console.log("responsive ok?", $attr.responsive == s);
if ($attr.responsive == s) {
if (!childScope) {
$transclude(function(clone, newScope) {
childScope = newScope;
clone[clone.length++] = document.createComment(' end responsive: ' + $attr.responsive + ' ');
block = {
clone: clone
};
$animate.enter(clone, $element.parent(), $element);
});
}
} else {
if (childScope) {
childScope.$destroy();
childScope = null;
}
if (block) {
block.clone.remove();
block.clone = null;
block = null;
}
}
});
}
};
});

AngularJS Pagination Directive w/ Isolate Scope

I have the following pagination directive written by a member of my team:
myApp.directive('pagination', function($timeout) {
return {
restrict: 'A',
controller: function($scope, $element, $attrs, $rootScope) {
var halfDisplayed = 1.5,
displayedPages = 3,
edges = 2;
$scope.getInterval = function() {
return {
start: Math.ceil($scope.currentPage > halfDisplayed ? Math.max(Math.min($scope.currentPage - halfDisplayed, ($scope.pages - displayedPages)), 0) : 0),
end: Math.ceil($scope.currentPage > halfDisplayed ? Math.min($scope.currentPage + halfDisplayed, $scope.pages) : Math.min(displayedPages, $scope.pages))
};
};
$scope.selectPage = function(pageIndex) {
$scope.currentPage = pageIndex;
$scope.$apply();
$scope.draw();
$scope.paginationUpdate();
};
$scope.appendItem = function(pageIndex, opts) {
var options, link;
pageIndex = pageIndex < 0 ? 0 : (pageIndex < $scope.pages ? pageIndex : $scope.pages - 1);
options = $.extend({
text: pageIndex + 1,
classes: ''
}, opts || {});
if (pageIndex === $scope.currentPage) {
link = $('<span class="current">' + (options.text) + '</span>');
} else {
link = $('' + (options.text) + '');
link.bind('click', function() {
$scope.selectPage(pageIndex);
});
}
if (options.classes) {
link.addClass(options.classes);
}
$element.append(link);
};
$rootScope.draw = function() {
$($element).empty();
var interval = $scope.getInterval(),
i;
// Generate Prev link
if (true) {
$scope.appendItem($scope.currentPage - 1, {
text: 'Prev',
classes: 'prev'
});
}
// Generate start edges
if (interval.start > 0 && edges > 0) {
var end = Math.min(edges, interval.start);
for (i = 0; i < end; i++) {
$scope.appendItem(i);
}
if (edges < interval.start) {
$element.append('<span class="ellipse">...</span>');
}
}
// Generate interval links
for (i = interval.start; i < interval.end; i++) {
$scope.appendItem(i);
}
// Generate end edges
if (interval.end < $scope.pages && edges > 0) {
if ($scope.pages - edges > interval.end) {
$element.append('<span class="ellipse">...</span>');
}
var begin = Math.max($scope.pages - edges, interval.end);
for (i = begin; i < $scope.pages; i++) {
$scope.appendItem(i);
}
}
// Generate Next link
if (true) {
$scope.appendItem($scope.currentPage + 1, {
text: 'Next',
classes: 'next'
});
}
};
},
link: function(scope, element, attrs, controller) {
$timeout(function() {
scope.draw();
}, 2000);
scope.$watch(scope.paginatePages, function() {
scope.draw();
});
},
template: '<div class="pagination-holder dark-theme">' + '</div>',
replace: true
};
});
Unfortunately, when he wrote it, the directive refered to controller variables and functions:
function PatientListModalConfigCtrl($scope, $rootScope, myService) {
$scope.currentPage = 0;
$scope.paginationUpdate = function() {
var list = myService.search($scope.currentPage + 1);
list.then(function(data) {
$rootScope.List = data[1];
$rootScope.pages = data[0];
});
};
};
I was able to replace the function call with $scope[](); using the attr parameter, but as soon as I try to add:
scope: {
currentPage: '=',
totalPages: '='
}
into the directive and isolate the directive from the controller, the pagination stops displaying altogether. Can I use the attr parameter for these two variables as well? I would prefer to use the scope in the directive because the variables will be changing, but my attempts have failed. I would appreciate any feedback.
Unfortunately, I have to say the whole directive looks a bit poorly designed. The dependancy on $rootScope is a big code smell, so is all the injecting of HTML in the controller.
To get on topic, you are two-way binding to primitives (integers). I am not entirely sure that's your entire problem, but it won't work as you expect. It's very logical once you think about it, how could AngularJS ever put any data back into (i.e update) a primitive?
By the look of it, you don't actually want two-way binding, you just need to send in some values. For such simple data you can define them as attributes ('#'), and then use attrs.$observe() to listen to the changes. As for the HTML, you can then use current-page="{{currentPage}}" to pass in the value (please note, it will be as a string, which you can then parse back).
Another option could be to pass in an object (using two-way binding). Example:
page = {
currentPage: 3,
totalPages: 14
}

AngularJS Simple Signature Pad Directive (without jQuery)

With the help of stackoverflow i got me a simple canvas signature directive. The problem is that it works with mouse events (mousedown, mouseup, mousemove) but is not working with touch events (touchstart,touchmove,touchend). I have ngTouch in my main app module and in the module that holds the directive. I hope you can help me. Here's the code:
var sig = angular.module('signature', ['ngTouch']);
sig.directive("mjav", ['$document', function ($document) {
return {
restrict: "A",
link: function (scope, element) {
var ctx = element[0].getContext('2d');
ctx.canvas.width = window.innerWidth - 20;
var tempCanvas = document.createElement('nanavas');
// variable that decides if something should be drawn on mousemove
var drawing = false;
// the last coordinates before the current move
var lastX;
var lastY;
element.on('touchstart', function (event) {
if (event.offsetX !== undefined) {
lastX = event.offsetX;
lastY = event.offsetY;
} else {
lastX = event.layerX - event.currentTarget.offsetLeft;
lastY = event.layerY - event.currentTarget.offsetTop;
}
// begins new line
ctx.beginPath();
drawing = true;
});
element.on('touchmove', function (event) {
if (drawing) {
// get current mouse position
if (event.offsetX !== undefined) {
currentX = event.offsetX;
currentY = event.offsetY;
} else {
currentX = event.layerX - event.currentTarget.offsetLeft;
currentY = event.layerY - event.currentTarget.offsetTop;
}
draw(lastX, lastY, currentX, currentY);
// set current coordinates to last one
lastX = currentX;
lastY = currentY;
}
});
$document.on('touchend', function (event) {
// stop drawing
drawing = false;
});
// canvas reset
function reset() {
element[0].width = element[0].width;
}
function draw(lX, lY, cX, cY) {
// line from
ctx.moveTo(lX, lY);
// to
ctx.lineTo(cX, cY);
// color
ctx.strokeStyle = "#000";
// draw it
ctx.stroke();
}
}
};
}]);
If someone will need a simple signature directive for AngularJS this is what I came up with in the end:
var sig = angular.module('signature', []);
sig.controller('signatureCtrl', ['$scope', function ($scope) {
$scope.clearVal = 0;
$scope.saveVal = 0;
$scope.clear = function () {
$scope.clearVal += 1; //On this value change directive clears the context
}
$scope.saveToImage = function () {
$scope.saveVal = 1; //On this value change directive saves the signature
}
}]);
sig.directive("signatureDir", ['$document', '$log', '$rootScope', function ($document, $log, $rootScope) {
return {
restrict: "A",
link: function (scope, element, attrs) {
var ctx = element[0].getContext('2d');
ctx.canvas.width = window.innerWidth - 30;
// the last coordinates before the current move
var lastPt;
function getOffset(obj) {
return { left: 15, top: 116 }; //Got a fixed offset
}
attrs.$observe("value", function (newValue) {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
});
attrs.$observe("saveVal", function (newValue, dnid) {
var imagedata = ctx.canvas.toDataURL();
$rootScope.signatureTemp.push({'dnid':dnid, 'signature':imagedata});
});
element.on('touchstart', function (e) {
e.preventDefault();
ctx.fillRect(e.touches[0].pageX - getOffset(element).left, e.touches[0].pageY - getOffset(element).top, 2, 2);
lastPt = { x: e.touches[0].pageX - getOffset(element).left, y: e.touches[0].pageY - getOffset(element).top };
});
element.on('touchmove', function (e) {
e.preventDefault();
if (lastPt != null) {
ctx.beginPath();
ctx.moveTo(lastPt.x, lastPt.y);
ctx.lineTo(e.touches[0].pageX - getOffset(element).left, e.touches[0].pageY - getOffset(element).top);
ctx.stroke();
}
lastPt = { x: e.touches[0].pageX - getOffset(element).left, y: e.touches[0].pageY - getOffset(element).top };
});
element.on('touchend', function (e) {
e.preventDefault();
lastPt = null;
});
}
};
}]);
Markup:
<div ng-controller="signatureCtrl">
<ul class="list-group">
<h3 style="padding-left: 15px;">Signature</h3>
<li class="list-group-item">
<canvas saveVal="{{ saveVal }}" value="{{ clearVal }}" style="border: 1px solid black;" id="canvas1" width="200" height="200" signatureDir></canvas>
<button class="btn btn-warning" ng-click="clear()">Clear</button>
<button class="btn btn-primary" ng-click="ok()">Save</button>
</li>
</ul>
</div>
If anyone can see some bad code in here please correct me!

Resources