Creating a directive for window resizing - angularjs

I have four mostly square looking charts which I need to resize on window resize. I have created a directive as below inside my angular controller at the top. This doesn't seem to work as the directive seem to executing as soon as the page loads and nothing seems to happen on resize. What am I missing here?
targetApp.directive('rsz', function ($window) {
return function (scope, element) {
var w = angular.element($window),
iw = w.innerWidth,
ih = w.innerHeight;
//resizing relative to the this parent container
scatterParentDimensions = getoffsetDimensions('#scatter-container'),
expectedWidth, expectedHeight;
console.log('rel w:', scatterParentDimensions.width, 'rel h:', scatterParentDimensions.height);
if (iw > ih) {
expectedWidth = scatterParentDimensions.width / 2;
expectedHeight = (scatterParentDimensions.height < iw)
? scatterParentDimensions.height / 2
: scatterParentDimensions.height / 4;
}
else {
expectedWidth = iw / 2;
expectedHeight = ih - 50;
}
// this is returning NaN as soon as the page is loaded.
console.log('set w:', expectedWidth, 'set h:', expectedHeight);
var selector = "#" + element.id;
if ($(selector).highcharts()) {
chart = $(selector).highcharts();
chart.setSize(expectedWidth, expectedHeight, false);
}
w.bind('resize', function () {
scope.$apply();
});
}
})
I have added the directive rsz on the four charts that need resizing.
//scatter.html
<div id="target-charts">
<div id="scatter-container" class="col-sm-8">
<div class="row">
<div class="col-sm-6">
<div class="scatter-chart">
<div class="chart-body" rsz id="scatterA"></div>
</div>
</div>
<div class="col-sm-6">
<div class="scatter-chart">
<div class="chart-body" rsz id="scatterB"></div>
</div>
</div>
<div class="col-sm-6">
<div class="scatter-chart">
<div class="chart-body" rsz id="scatterC"></div>
</div>
</div>
<div class="col-sm-6">
<div class="scatter-chart">
<div class="chart-body" rsz id="scatterD"></div>
</div>
</div>
</div>
</div>
</div>
Definition for getOffsetDimensions
function getoffsetDimensions(selector) {
var el = document.querySelector(selector);
return {
width: el.offsetWidth,
height: el.offsetHeight
}
}

in your directive, you listen to the resize event but didn't do anything (scope.$apply basically does nothing here).
you can either do the actually resizing inside of event handler, for example
myapp.directive('resize1', function($window) {
return function(scope, element, attr) {
var w = angular.element($window);
w.on('resize', function() {
var hh = document.getElementById('header').offsetHeight;
var fh = document.getElementById('footer').offsetHeight;
console.log('hh & fh', hh, fh);
var tp = hh + 2;
var bt = fh + 2;
console.log('tp & bt', tp, bt);
var changes = {
bottom: bt + 'px',
top: tp + 'px',
}
element.css(changes);
scope.$apply();
});
};
});
or watch the dimension changes, for example
app.directive('resize', function ($window) {
return function (scope, element) {
var w = angular.element($window);
scope.getWindowDimensions = function () {
return {
'h': w.height(),
'w': w.width()
};
};
scope.$watch(scope.getWindowDimensions, function (newValue, oldValue) {
scope.windowHeight = newValue.h;
scope.windowWidth = newValue.w;
scope.style = function () {
return {
'height': (newValue.h - 100) + 'px',
'width': (newValue.w - 100) + 'px'
};
};
}, true);
w.bind('resize', function () {
scope.$apply();
});
}
})

Eventually this is how I have made this work. It is still not the perfect solution and not at all generic, but I got it work for the initial stage of what I was looking for.
targetApp.directive('resize', function ($window) {
return function (scope, element, attr) {
var w = angular.element($window);
scope.$watch(function () {
return {
'h': window.innerHeight,
'w': window.innerWidth
};
}, function (newValue, oldValue) {
var ew, eh, scatterParentDimensions = getoffsetDimensions('#scatter-container');
if (newValue.w > newValue.h) {
ew = scatterParentDimensions.width / 2;
eh = (scatterParentDimensions.height < newValue.w)
? scatterParentDimensions.height / 2
: scatterParentDimensions.height / 4;
}
else {
ew = newValue.w / 2;
eh = newValue.h - 50;
}
//resize highcharts
var selector = '#' + element[0].id;
var chart = angular.element(selector).highcharts();
chart.setSize(ew, eh, false);
}, true);
w.bind('resize', function () {
scope.$apply();
});
}
});

Related

Modifying elements with same directive

I have several elements in a container. One of the rows has two icons in it: zoom in and zoom out. When you click Zoom In, I'd like all the row's widths to grow.
<div id="events">
<year>year 1</year>
<year>year 2</year>
<year>year 3</year>
<year>year 4</year>
<div id="scaling">
<md-icon aria-label="Zoom In" class="material-icons" ng-click="zoomIn()">zoom_in</md-icon>
<md-icon aria-label="Zoom Out" class="material-icons" ng-click="zoomOut()">zoom_out</md-icon>
</div>
</div>
I have a year directive:
angular.module("app").directive("year", ['$rootScope', function ($rootScope) {
return {
link: function($scope, element, attr) {
var events = element;
$scope.zoomIn = function(ev) {
console.log('zoomin');
$scope.zoom = $scope.zoom + $scope.scale;
if($scope.zoom < 100) { $scope.zoom = 100; }
events.html($scope.zoom);
events.css({
'width': $scope.zoom + '%'
});
}
$scope.zoomOut = function(ev) {
$scope.zoom = $scope.zoom - $scope.scale;
if($scope.zoom < 100) { $scope.zoom = 100; }
events.css({
'width': $scope.zoom + '%'
});
}
}
}
}]);
However the width is only applied to the very last year element. Why is that?
You are overwriting the scope every time. So each instance of your year directive is clobbering the zoomIn and zoomOut methods each time it is instantiated.
Normally you could solve this by using a new or isolate scope in your directive definition object:
//new scope
{
scope: true
}
//isolate scope
{
scope: {}
}
However, since you want to bind click handlers outside your individual year directives you will have to do something else.
A better solution would be to pass in the attributes and simply respond to their changes:
return {
scope: {
zoom: '='
},
link: function(scope, elem, attrs){
scope.$watch('zoom', function(){
//Do something with 'scope.zoom'
});
}
};
Now your external zoomIn and zoomOut functions can just modify some zoom property on the parent scope, and you can bind your year components to that.
<year zoom="myZoomNumber"></year>
And just for posterity, here is a working snippet.
function EventsController() {
var $this = this;
var zoom = 1;
$this.zoom = zoom;
$this.zoomIn = function() {
zoom *= 1.1;
$this.zoom = zoom;
console.log({
name: 'zoomIn',
value: zoom
});
};
$this.zoomOut = function() {
zoom *= 0.9;
$this.zoom = zoom;
console.log({
name: 'zoomOut',
value: zoom
});
};
}
function YearDirective() {
return {
restrict: 'E',
template: '<h1 ng-transclude></h1>',
transclude: true,
scope: {
zoom: '='
},
link: function(scope, elem, attr) {
var target = elem.find('h1')[0];
scope.$watch('zoom', function() {
var scaleStr = "scale(" + scope.zoom + "," + scope.zoom + ")";
console.log({
elem: target,
transform: scaleStr
});
target.style.transform = scaleStr;
target.style.transformOrigin = 'left';
});
}
};
}
var mod = angular.module('my-app', []);
mod
.controller('eventsCtrl', EventsController)
.directive('year', YearDirective);
.scaling{
z-index:1000;
position:fixed;
top:10px;
left:10px;
}
.behind{
margin-top:50px;
z-index:-1;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<div ng-app="my-app" ng-controller="eventsCtrl as $ctrl">
<div class="scaling">
<button type="button" aria-label="Zoom In" ng-click="$ctrl.zoomIn()">zoom_in</button>
<button type="button" aria-label="Zoom Out" ng-click="$ctrl.zoomOut()">zoom_out</button>
</div>
<div class="behind">
<year zoom="$ctrl.zoom">year 1</year>
<year zoom="$ctrl.zoom">year 2</year>
<year zoom="$ctrl.zoom">year 3</year>
<year zoom="$ctrl.zoom">year 4</year>
</div>
</div>
The events.css is getting over-ridden, thus making it apply only to last element.
events.css({
'width': $scope.zoom + '%'
}).bind(this);
You have to bind it to current scope.

Define and Watch a variable according to windowidth

I'm struggling creating a directive to assign and update a variable, that compares to the window width, and updates with resize.
I need the variable as compared to using CSS because I will work it into ng-if. What am I doing wrong? Here is the code:
var app = angular.module('miniapp', []);
function AppController($scope) {}
app.directive('widthCheck', function ($window) {
return function (scope, element, attr) {
var w = angular.element($window);
scope.$watch(function () {
return {
'w': window.innerWidth
};
}, function (newValue, oldValue, desktopPlus, isMobile) {
scope.windowWidth = newValue.w;
scope.desktopPlus = false;
scope.isMobile = false;
scope.widthCheck = function (windowWidth, desktopPlus) {
if (windowWidth > 1399) {
scope.desktopPlus = true;
}
else if (windowWidth < 769) {
scope.isMobile = true;
}
else {
scope.desktopPlus = false;
scope.isMoblie = false;
}
}
}, true);
w.bind('resize', function () {
scope.$apply();
});
}
});
JSfiddle here: http://jsfiddle.net/h8m4eaem/2/
As mentioned in this SO answer it's probably better to bind to the window resize event with-out watch. (Similar to Mr. Berg's answer.)
Something like in the demo below or in this fiddle should work.
var app = angular.module('miniapp', []);
function AppController($scope) {}
app.directive('widthCheck', function($window) {
return function(scope, element, attr) {
var width, detectFalse = {
desktopPlus: false,
isTablet: false,
isMobile: false
};
scope.windowWidth = $window.innerWidth;
checkSize(scope.windowWidth); // first run
//scope.desktopPlus = false;
//scope.isMoblie = false; // typo
//scope.isTablet = false;
//scope.isMobile = false;
function resetDetection() {
return angular.copy(detectFalse);
}
function checkSize(windowWidth) {
scope.detection = resetDetection();
if (windowWidth > 1000) { //1399) {
scope.detection.desktopPlus = true;
} else if (windowWidth > 600) {
scope.detection.isTablet = true;
} else {
scope.detection.isMobile = true;
}
}
angular.element($window).bind('resize', function() {
width = $window.innerWidth;
scope.windowWidth = width
checkSize(width);
// manuall $digest required as resize event
// is outside of angular
scope.$digest();
});
}
});
.fess {
border: 1px solid red;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="miniapp" ng-controller="AppController">
<div width-check class="fess" resize>
window.width: {{windowWidth}}
<br />desktop plus: {{detection.desktopPlus}}
<br />mobile: {{detection.isMobile}}
<br />tablet: {{detection.isTablet}}
<br/>
</div>
</div>
scope.widthCheck is assigned an anonymous function and RE-ASSIGNED that same function each time this watcher fires. I also notice it's never called.
you should move that piece out of the $watch and call the function when the watcher fires
There's no need for a watch, you're already binding to resize. Just move your logic in there. And as Jun Duan said you continously create the funciton. Here's the change:
app.directive('widthCheck', function ($window) {
return function (scope, element, attr) {
function widthCheck(windowWidth) {
if (windowWidth > 1399) {
scope.desktopPlus = true;
}
else if (windowWidth < 769) {
scope.isMobile = true;
}
else {
scope.desktopPlus = false;
scope.isMoblie = false;
}
}
windowSizeChanged();
angular.element($window).bind('resize', function () {
windowSizeChanged();
scope.$apply();
});
function windowSizeChanged(){
scope.windowWidth = $window.innerWidth;
scope.desktopPlus = false;
scope.isMobile = false;
widthCheck(scope.windowWidth);
}
}
});
jsFiddle: http://jsfiddle.net/eqd3zu0j/

Change Text in directive

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.

Angular upload file directive

I need a directive for upload file (brwose to choose file, and get it as binary data ) , in my angular application.
I use it a lot of times, so it has to be generic.
What is the best way to do it?
https://github.com/flowjs/ng-flow
This works fine.Of course some wrapping directive can be made at your side.
directive code
.directive("fileReaderGallery", ['background', function (background) {
return {
restrict: "E",
scope: true,
templateUrl: "templates/directive.html",
replace: true,
link: function ($scope, el, attrs) {
var input = null,
drag_image_gallery = el.find('.drag_image_gallery');
$scope.dragging = false;
$scope.fileselected = false;
$scope.uploaded = false;
$scope.uploading = false;
$scope.image = null;
$scope.clearFileReader = function () {
if (!attrs.styling || !input) return false;
$scope.formTitan.elementStyles[attrs.styling][$scope.styledItem.pt]['background-image'] = '';
$scope.formSelected[attrs.styling].imageFile = null;
$scope.formSelected[attrs.styling].isImage = false;
input.value = '';
$scope.fileselected = false;
$scope.imageName = '';
};
var readfiles = function (files) {
var reader, bg;
if (files && files[0]) {
if (files.length > 1) {
return console.log("Select single file only");
}
reader = new FileReader;
reader.onload = function (e) {
if (files[0].type.indexOf('image/') !== -1) {
if (e.target.result) {
bg = {
'background-image': e.target.result,
'background-repeat': 'repeat',
'background-position': 'top',
'background-size': ''
};
$scope.uploading = true;
$scope.$apply(function () {
background.add(angular.copy(bg));
$scope.current.dcBGImage = angular.copy(bg);
$scope.imageName = files[0].name;
$scope.image = e.target.result;
$scope.fileselected = true;
console.log(files[0])
});
}
} else {
return console.log('Please select an Image');
}
};
return reader.readAsDataURL(files[0]);
}
};
$scope.clickUpload = function () {
el.find('.bg-file-reader').click();
};
drag_image_gallery[0].ondragover = function () {
$scope.dragging = true;
//drag_image_gallery.addClass('ondragover');
$scope.$digest();
return false;
};
drag_image_gallery[0].ondragleave = function () {
$scope.dragging = false;
$scope.$digest();
//drag_image_gallery.removeClass('ondragover');
};
drag_image_gallery[0].ondrop = function (e) {
$scope.dragging = false;
$scope.$digest();
//drag_image_gallery.removeClass('ondragover');
e.preventDefault();
readfiles(e.dataTransfer.files);
};
el.find('.bg-file-reader').on('change', function () {
readfiles(this.files);
});
}
};
}]);
html template code
<div class="row upload_image text-center">
<div class="drag_image drag_image_gallery row text-center" ng-class=" {ondragover:dragging}">
Drag an Image here
</div>
OR
<div class="row text-center choose_computer">
<button ng-click="clickUpload()" class="btn btn-default">Choose from your computer</button>
<input type="file" class="bg-file-reader upload" name="gallery"/>
</div>
</div>
directive with drag and drop and chose file both functionality

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