Angular JS resizable div directive - angularjs

My site will have multiple sections, each of which I intend to be resizable. To accomplish this I've made a "resizable" directive, e.g.:
<div class="workspace" resize="full" ng-style="resizeStyle()">
<div class="leftcol" resize="left" ng-style="resizeStyle()">
With a directive that looks something like:
lwpApp.directive('resize', function ($window) {
return {
scope: {},
link: function (scope, element, attrs) {
scope.getWinDim = function () {
return {
'height': window.height(),
'width': window.width()
};
};
// Get window dimensions when they change and return new element dimensions
// based on attribute
scope.$watch(scope.getWinDim, function (newValue, oldValue) {
scope.resizeStyle = function () {
switch (attrs.resize) {
case 'full':
return {
'height': newValue.height,
'width': (newValue.width - dashboardwidth)
};
case 'left':
return {
'height': newValue.height,
'width': (newValue.width - dashboardwidth - rightcolwidth)
};
etc...
};
}, true);
//apply size change on window resize
window.bind('resize', function () {
scope.$apply(scope.resizeStyle);
});
}
};
});
As you can see, this only resizes each div on window resize, and each directive has an isolate scope. This works fine for what it's built for, but ultimately I would like to make a subset of the divs resizable via a draggable bar. For instance
div1 div2
----------------
| || |
| || |
| || |
| || |
----------------
draggable bar in middle
On the the draggable bar's movement (in the horizontal direction), I would need to access both div1, div2's width presumably via the scope of a parent controller(?). My questions are:
Is this the "correct" way to go about making resizable divs in angular? In particular, when the size of one div affects another?
I personally feel like the answer to (1) is "No, I am not doing it correctly because I cannot communicate between directives when each has an isolate scope." If this is true, how can I account for both window and draggable resizing between divs?

This question is old, but for anybody looking for a solution, I built a simple directive to handle this, for vertical and horizontal resizers.
Take a look at the Plunker
angular.module('mc.resizer', []).directive('resizer', function($document) {
return function($scope, $element, $attrs) {
$element.on('mousedown', function(event) {
event.preventDefault();
$document.on('mousemove', mousemove);
$document.on('mouseup', mouseup);
});
function mousemove(event) {
if ($attrs.resizer == 'vertical') {
// Handle vertical resizer
var x = event.pageX;
if ($attrs.resizerMax && x > $attrs.resizerMax) {
x = parseInt($attrs.resizerMax);
}
$element.css({
left: x + 'px'
});
$($attrs.resizerLeft).css({
width: x + 'px'
});
$($attrs.resizerRight).css({
left: (x + parseInt($attrs.resizerWidth)) + 'px'
});
} else {
// Handle horizontal resizer
var y = window.innerHeight - event.pageY;
$element.css({
bottom: y + 'px'
});
$($attrs.resizerTop).css({
bottom: (y + parseInt($attrs.resizerHeight)) + 'px'
});
$($attrs.resizerBottom).css({
height: y + 'px'
});
}
}
function mouseup() {
$document.unbind('mousemove', mousemove);
$document.unbind('mouseup', mouseup);
}
};
});

I know I'm a bit late to the party, but I found this and needed my own solution. If you're looking for a directive that works with flexbox, and doesn't use jquery. I threw one together here:
http://codepen.io/Reklino/full/raRaXq/
Just declare which directions you want the element to be resizable from, and whether or not you're using flexbox (defaults to false).
<section resizable r-directions="['right', 'bottom']" r-flex="true">

For the needs of my project i added support of minimum values, so that panels can keep some width or height - (here is the gist) -
Github
Also, i created Github repo, where i added support for panels being located right of main page axis and support of minimum/maximum values. It's in example stage now, but i'm willing to turn it into a full-weight Angular directive

This does not completely answer the question, but changing scope: true solved the isolate scope problem. In particular, in my html I have:
<div ng-controller="WorkspaceCtrl">
<div class="workspace" resize="full" ng-style="resizeStyle()">
<div class="leftcol" resize="left" ng-style="resizeStyle()">
<ul class="filelist">
<li ng-repeat="file in files" id={{file.id}} ng-bind=file.name></li>
</ul>
<div contenteditable="true" ng-model="content" resize="editor" ng-style="resizeStyle()">
Talk to me
</div>
</div>
</div>
and ng-repeat="file in files" still has access to the array $scope.files defined in the controller WorkspaceCtrl. So scope: {} cuts off the scope of the directive from the scope of the parent controller, whereas scope: true simply creates a new scope for each instance of the directive AND each instance of the directive, along with its children, retains access to the parent scope.
I have not yet implemented the draggable bar which resizes these divs, but will report back when I do so.

Related

Leaflet issues angular woes in showing and hiding? Want to get rid of $timeout

I have a leaflet being created with L.map('mapelement') being called. The issue is that if I click a button that "hides" the leaflet map, then click the button again to show, the leaflet map does not show up. However, when I put in a setTimeout within the link function before the map gets created and set it to 2 seconds, then the map shows every time (though I have to wait 2 seconds). Is there a better alternative to using $timeout in my custom "leaflet-map" directive to show and hide?
I created a naive example of a leaflet-map directive without seeing any of your code and am toggling the display of the map through ng-show. It works without any $timeout. It's hard to diagnose where your problems are stemming from without seeing any code or knowing how you are trying to toggle the map's display.
angular.module('demo', [])
.directive('leafletMap', function() {
return {
restrict: 'E',
scope: {
mapOptions: '&'
},
template: '<div><button ng-click="toggleShow()">Toggle Map</button><div class="demo-map" ng-show="isShown"></div></div>',
link: function(scope, elem, attrs) {
// Find element to bind to map
var mapElem = elem.children().find('div')[0],
// get map options from isolate scope
mapOptions = scope.mapOptions();
// State of hide/show
scope.isShown = true;
// Create Map.
var map = L.map(mapElem, mapOptions);
// Just taken from leaflet example
L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpandmbXliNDBjZWd2M2x6bDk3c2ZtOTkifQ._QA7i5Mpkd_m30IGElHziw', {
maxZoom: 18,
attribution: 'Map data © OpenStreetMap contributors, ' +
'CC-BY-SA, ' +
'Imagery © Mapbox',
id: 'mapbox.streets'
}).addTo(map);
// method to toggle the shown/hidden state of map
scope.toggleShow = function() {
scope.isShown = !scope.isShown;
};
// cleanup on element destroy
elem.on('$destroy', function() {
map.remove();
});
}
};
})
.controller('DemoController', function() {
this.mapOptions = {
center: [51.505, -0.09],
zoom: 13
};
});
.demo-map {
height: 500px;
}
<script src="//cdn.leafletjs.com/leaflet/v0.7.7/leaflet.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<link rel="stylesheet" href="//cdn.leafletjs.com/leaflet/v0.7.7/leaflet.css" />
<div ng-app="demo" ng-controller="DemoController as ctrl">
<leaflet-map map-options="ctrl.mapOptions"></leaflet-map>
</div>
Would CSS help you ?
Create one map in a visible div
visibility: visible
Create the second map in a hidden div
visibility: hidden
Position both your div in the same position
position: absolute
When you want to toggle just change the visibility of your divs
Example: http://plnkr.co/edit/voTjyMLKTxUC183nf8ML?p=preview
(Sorry it's not angular)

How to get progressbar working?

I am just starting out with angular and trying to create a progressbar with a directive:
var module = angular.module("app", []);
module.directive("progressbar", function () {
return {
restrict: "A",
link: function (scope, element) {
//debugger;
for (var i = 0; i<100;i++) {
console.log(i);
element.css("width", i / 100 + "%");
}
}
};
});
HTML
<div ng-app="app">
<div class="progress-bar progress-bar-warning" progressbar></div>
</div>
It enters the loop but nothing is displaying? This is a link to a fiddle:
http://jsfiddle.net/dingen2010/fg229svz/23/
First of all, your progress-bar doesn't have height, so its height is 0px. Please set some height.
Secondly, your progress should be (i+1) not (i/100), since width is from 0% to 100%. Otherwise, width would be from 0.01% to 0.99%.
Thirdly, I think you misunderstand what link is for. link must be completed before any directive is rendered, so you won't see progress-bar growing animation. The app will waits for loop inside link to finish before displaying.
What you need to do is $watch some directive attribute for loading progress. There are many way to implement this, this is just one of the way:
http://jsfiddle.net/dmqnqkkn/2/

Dynamically added directive is not interpolated

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?

how to get event when user scroll to top in angular js?

could you please tell me how to get event when user scroll to top .Actually I am using ng-repeat in my example .I want to get event when user scroll to bottom and scroll to top .I have one div in which I used ng-repeat can we get event of top when user move to top after scrolling.Actually I need to show alert when user scroll to bottom and top of div in angular .here is my code
<body ng-controller="MyController">
<div style="width:90%;height:150px;border:1px solid red;overflow:auto">
<div ng-repeat="n in name">{{n.name}}</div>
</div>
You could put directives on your scrollable div that listen to the scroll event and check for the top or bottom being reached.
So, using your HTML, your div would look like this:
<div exec-on-scroll-to-top="ctrl.handleScrollToTop"
exec-on-scroll-to-bottom="ctrl.handleScrollToBottom"
style="width:90%;height:150px;border:1px solid red;overflow:auto">
<div ng-repeat="n in name">{{n.name}}</div>
</div>
With new directives exec-on-scroll-to-top and exec-on-scroll-to-bottom added. Each specifies a function in your controller that should execute when the particular event the directive is checking for occurs.
exec-on-scroll-to-top would look like this, just checking for the scrollable div's scrollTop property to be 0:
myapp.directive('execOnScrollToTop', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var fn = scope.$eval(attrs.execOnScrollToTop);
element.on('scroll', function (e) {
if (!e.target.scrollTop) {
console.log("scrolled to top...");
scope.$apply(fn);
}
});
}
};
});
And exec-on-scroll-to-bottom would look like this (keeping in mind that an element is fully scrolled when its (scrollHeight - scrollTop) === clientHeight):
myapp.directive('execOnScrollToBottom', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var fn = scope.$eval(attrs.execOnScrollToBottom),
clientHeight = element[0].clientHeight;
element.on('scroll', function (e) {
var el = e.target;
if ((el.scrollHeight - el.scrollTop) === clientHeight) { // fully scrolled
console.log("scrolled to bottom...");
scope.$apply(fn);
}
});
}
};
});
Here's a plunk. Open the console to see messages getting logged when the scrolling reaches the top or bottom.
This is a non angular way, but you can wrap it up in a directive which also allows reuse:
Use Javascript event listener:
div.addEventListener('scroll', function(){
if(this.scrollTop===0)
//do your stuff
});
Make sure to use $apply if you make any changes to the scope variables inside this listener.

How to get on('mousedown', ....) from the child element in angularjs

I got this simple drag example from angularjs docs.
here is a plunk fork
However, I am trying to get to the child node's actions so it will drag only when clicked on the child element. I have tried :
var elementDrag=element[0].getElementsByClassName('dragThis');
elementDrag.on('mousedown', function(event) {
// Prevent default dragging of selected content
event.preventDefault();
startX = event.pageX - x;
startY = event.pageY - y;
$document.on('mousemove', mousemove);
$document.on('mouseup', mouseup);
});
Any ideas on how to approach this without using jQuery?
Here is a quick and dirty implementation to get you started with: http://plnkr.co/edit/1hBmpg51xqzxi0EP4WBg
Try it out and let me know if you still have questions. The controller code needs some cleaning ;-).
The implementation is based on two directives that communicate with each other. The outer directive (draggable-content) exposes an API allowing the inner directive (draggable-control) to perform de drag.
.directive('draggableControl', function($document) {
return {
require: '^draggableContent',
// The 4th arg of the link fn, ctrl, is the controller of the outer directive draggableContent
link: function(scope, element, attr, ctrl) {
// more code
}
};
})
The markup is straightforward:
<body ng-app="drag">
<div draggable-content>
<div draggable-control class="dragThis" style='border: 1px solid yellow; background:white;'>Drag here only</div>
DO NOT drag here<br><br><br>or here</div>
</body>
Based on #apairet plunkr, I was able to finish the directive.
Here is what I fixed:
function mouseup() {
$document.off('mousemove', ctrl.mousemove);
$document.off('mouseup', mouseup);
ctrl.y = event.screenY - ctrl.startY;
ctrl.x = event.screenX - ctrl.startX;
}
Thank you, I learned about "^require".

Resources